@ohos-cpf/3rdloop 0.0.2 → 0.0.3

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 (393) hide show
  1. package/README.md +244 -244
  2. package/bin/3rdloop.mjs +46 -46
  3. package/lib/cli.js +781 -781
  4. package/lib/config-cmd.js +451 -451
  5. package/lib/config.js +318 -318
  6. package/lib/doctor.js +113 -113
  7. package/lib/exit-codes.js +30 -30
  8. package/lib/opencode.js +155 -155
  9. package/lib/orch.js +426 -426
  10. package/lib/queries.js +263 -263
  11. package/lib/step.js +325 -325
  12. package/lib/ui.js +222 -222
  13. package/lib/update.js +279 -279
  14. package/lib/workflow.js +308 -308
  15. package/lib/workflows.js +131 -131
  16. package/package.json +50 -50
  17. package/vendor/Server/Agent/SkillSelector/README.md +343 -343
  18. package/vendor/Server/Agent/SkillSelector/core/Agent.js +207 -207
  19. package/vendor/Server/Agent/SkillSelector/index.js +14 -14
  20. package/vendor/Server/Agent/SkillSelector/llm/llmClient.js +313 -313
  21. package/vendor/Server/Agent/SkillSelector/llm/prompts.js +161 -161
  22. package/vendor/Server/Agent/SkillSelector/llm/reactSteps.js +144 -144
  23. package/vendor/Server/Agent/SkillSelector/matcher/fuzzyMatcher.js +433 -433
  24. package/vendor/Server/Agent/SkillSelector/scanner/skillScanner.js +382 -382
  25. package/vendor/Server/Agent/SkillSelector/utils/actionWords.js +59 -59
  26. package/vendor/Server/Brain/AnalysisStrategies.js +422 -422
  27. package/vendor/Server/Brain/Brain.js +686 -686
  28. package/vendor/Server/Brain/DecompositionStrategies.js +621 -621
  29. package/vendor/Server/Brain/PlanPersistence.js +196 -196
  30. package/vendor/Server/Brain/SkillExecutor.js +251 -251
  31. package/vendor/Server/Brain/index.js +11 -11
  32. package/vendor/Server/CLI/cli.js +275 -275
  33. package/vendor/Server/CLI/deveco-code/index.js +71 -71
  34. package/vendor/Server/CLI/deveco-code.js +5 -5
  35. package/vendor/Server/CLI/opencode/index.js +817 -818
  36. package/vendor/Server/CLI/opencode.js +5 -5
  37. package/vendor/Server/DbUse/MessageArchive.js +61 -61
  38. package/vendor/Server/DbUse/StorageManager.js +107 -107
  39. package/vendor/Server/DbUse/adapters/BaseAdapter.js +94 -94
  40. package/vendor/Server/DbUse/adapters/FileAdapter.js +138 -138
  41. package/vendor/Server/DbUse/adapters/JsonAdapter.js +138 -138
  42. package/vendor/Server/DbUse/adapters/JsonlAdapter.js +114 -114
  43. package/vendor/Server/DbUse/adapters/SqliteAdapter.js +157 -157
  44. package/vendor/Server/DbUse/index.js +160 -160
  45. package/vendor/Server/DbUse/stores/SessionStore.js +279 -279
  46. package/vendor/Server/DbUse/stores/TaskExecutionStore.js +133 -133
  47. package/vendor/Server/DbUse/stores/TaskRegistryStore.js +262 -262
  48. package/vendor/Server/DbUse/stores/TaskStepStore.js +279 -279
  49. package/vendor/Server/FlexRunner/FlexRunner.js +1759 -1759
  50. package/vendor/Server/FlexRunner/index.js +12 -12
  51. package/vendor/Server/Knowledge/KnowledgeImporter.js +523 -523
  52. package/vendor/Server/Knowledge/KnowledgeManager.js +341 -341
  53. package/vendor/Server/Knowledge/index.js +33 -33
  54. package/vendor/Server/LoopEngine/LoopEngine.js +1133 -1133
  55. package/vendor/Server/LoopEngine/index.js +11 -11
  56. package/vendor/Server/Orchestrator/Orchestrator.js +0 -3
  57. package/vendor/Server/Orchestrator/StepNode.js +312 -312
  58. package/vendor/Server/Orchestrator/index.js +13 -13
  59. package/vendor/Server/Routes/controllers/BrainController.js +241 -241
  60. package/vendor/Server/Routes/controllers/FlexRunnerController.js +1125 -1125
  61. package/vendor/Server/Routes/controllers/HealthController.js +44 -44
  62. package/vendor/Server/Routes/controllers/KnowledgeImportController.js +355 -355
  63. package/vendor/Server/Routes/controllers/LoopEngineController.js +788 -788
  64. package/vendor/Server/Routes/controllers/StatsController.js +57 -57
  65. package/vendor/Server/Routes/controllers/StreamController.js +67 -67
  66. package/vendor/Server/Routes/library/logger.js +179 -179
  67. package/vendor/Server/Routes/library/zipArchive.js +172 -172
  68. package/vendor/Server/Routes/router/Router.js +204 -204
  69. package/vendor/Server/Routes/routes/brain.js +47 -47
  70. package/vendor/Server/Routes/routes/flexrunner.js +72 -72
  71. package/vendor/Server/Routes/routes/health.js +28 -28
  72. package/vendor/Server/Routes/routes/index.js +57 -57
  73. package/vendor/Server/Routes/routes/knowledge.js +55 -55
  74. package/vendor/Server/Routes/routes/loopengine.js +58 -58
  75. package/vendor/Server/Routes/routes/orchestrator.js +61 -61
  76. package/vendor/Server/Routes/routes/registry.js +97 -97
  77. package/vendor/Server/Routes/routes/stats.js +23 -23
  78. package/vendor/Server/Routes/routes/stream.js +30 -30
  79. package/vendor/Server/Routes/server.js +203 -203
  80. package/vendor/Server/Routes/sse/SseHub.js +308 -308
  81. package/vendor/Server/Skills/acceptance-check/SKILL.md +191 -191
  82. package/vendor/Server/Skills/acceptance-check/references/PHASE1_ARTIFACT_COLLECTION.md +52 -52
  83. package/vendor/Server/Skills/acceptance-check/references/PHASE2_CRITERIA_EVALUATION.md +77 -77
  84. package/vendor/Server/Skills/acceptance-check/references/PHASE2_EVIDENCE_VERIFICATION.md +70 -70
  85. package/vendor/Server/Skills/arkts-code-check/SKILL.md +320 -320
  86. package/vendor/Server/Skills/arkts-code-check/references/CODE_CHECK_GUIDE.md +210 -210
  87. package/vendor/Server/Skills/arkts-code-check/references/FILE_TYPE_GUIDE.md +28 -28
  88. package/vendor/Server/Skills/arkts-code-check/references/PHASE1_INFO_COLLECTION.md +233 -233
  89. package/vendor/Server/Skills/arkts-code-check/references/REPORT_TEMPLATE.md +219 -219
  90. package/vendor/Server/Skills/arkts-library-compile-validate/SKILL.md +328 -328
  91. package/vendor/Server/Skills/arkts-library-compile-validate/scripts/build.ps1 +93 -93
  92. package/vendor/Server/Skills/arkts-library-compile-validate/scripts/code-linter.cjs +432 -432
  93. package/vendor/Server/Skills/arkts-library-compile-validate/scripts/migrate.cjs +851 -851
  94. package/vendor/Server/Skills/arkts-library-compile-validate/scripts/ohpm-switch.cjs +788 -788
  95. package/vendor/Server/Skills/arkts-library-demo-code/SKILL.md +291 -291
  96. package/vendor/Server/Skills/arkts-library-demo-code/references/API_SIGNATURE_CHECKS.md +68 -68
  97. package/vendor/Server/Skills/arkts-library-demo-code/references/BUILD_VALIDATION.md +184 -184
  98. package/vendor/Server/Skills/arkts-library-demo-code/references/CODE_STANDARDS.md +337 -337
  99. package/vendor/Server/Skills/arkts-library-demo-code/references/EXAMPLES.md +400 -400
  100. package/vendor/Server/Skills/arkts-library-demo-code/references/SYSTEM_API_LOOKUP.md +98 -98
  101. package/vendor/Server/Skills/arkts-library-demo-code/references/TEMPLATE.md +544 -544
  102. package/vendor/Server/Skills/arkts-library-demo-code/scripts/build.ps1 +93 -93
  103. package/vendor/Server/Skills/arkts-library-demo-code/scripts/code-linter.cjs +432 -432
  104. package/vendor/Server/Skills/arkts-library-demo-code/scripts/migrate.cjs +851 -851
  105. package/vendor/Server/Skills/arkts-library-demo-code/scripts/ohpm-switch.cjs +788 -788
  106. package/vendor/Server/Skills/arkts-library-demo-coverage/SKILL.md +174 -174
  107. package/vendor/Server/Skills/arkts-library-demo-scenario/SKILL.md +321 -321
  108. package/vendor/Server/Skills/arkts-library-demo-scenario/references/EXAMPLES.md +100 -100
  109. package/vendor/Server/Skills/arkts-library-demo-scenario/references/TEMPLATE.md +185 -185
  110. package/vendor/Server/Skills/arkts-library-documentation-quality-check/SKILL.md +233 -233
  111. package/vendor/Server/Skills/arkts-library-documentation-quality-check/assets/REPORT_TEMPLATE.md +102 -102
  112. package/vendor/Server/Skills/arkts-library-documentation-quality-check/references/CHECK_DIMENSIONS.md +89 -89
  113. package/vendor/Server/Skills/arkts-library-documentation-quality-check/references/COMPLIANCE_CHECKLIST.md +62 -62
  114. package/vendor/Server/Skills/arkts-library-documentation-quality-check/references/KB_QUERY_GUIDE.md +107 -107
  115. package/vendor/Server/Skills/arkts-library-documentation-quality-check/references/MCP_TOOL_DISCOVERY.md +124 -124
  116. package/vendor/Server/Skills/arkts-library-documentation-quality-check/references/REPORT_TEMPLATE.md +90 -90
  117. package/vendor/Server/Skills/arkts-library-documentation-quality-check/references/SCORING_FORMULA.md +57 -57
  118. package/vendor/Server/Skills/arkts-library-documentation-quality-check/scripts/validate-skill.cjs +259 -259
  119. package/vendor/Server/Skills/arkts-library-interface-spec/SKILL.md +97 -97
  120. package/vendor/Server/Skills/arkts-library-memory-leak-check/SKILL.md +391 -391
  121. package/vendor/Server/Skills/arkts-library-source-clone/SKILL.md +113 -113
  122. package/vendor/Server/Skills/arkts-library-stability-check/SKILL.md +312 -312
  123. package/vendor/Server/Skills/arkts-library-stability-check/assets/REPORT_TEMPLATE.md +154 -154
  124. package/vendor/Server/Skills/arkts-library-stability-check/references/CHECKLIST.md +165 -165
  125. package/vendor/Server/Skills/arkts-library-stability-check/references/CODE_EXAMPLES.md +308 -308
  126. package/vendor/Server/Skills/arkts-library-stability-check/references/SCORING_RULES.md +90 -90
  127. package/vendor/Server/Skills/arkts-library-stability-check/scripts/validate-skill.cjs +282 -282
  128. package/vendor/Server/Skills/arkts-library-test-coverage-check/SKILL.md +360 -360
  129. package/vendor/Server/Skills/arkts-library-test-coverage-check/assets/REPORT_TEMPLATE.md +145 -145
  130. package/vendor/Server/Skills/arkts-library-test-coverage-check/references/CHECKLIST.md +252 -252
  131. package/vendor/Server/Skills/arkts-library-test-coverage-check/references/MCP_TOOL_USAGE.md +114 -114
  132. package/vendor/Server/Skills/arkts-library-test-coverage-check/scripts/validate-skill.cjs +262 -262
  133. package/vendor/Server/Skills/arkts-library-xts-code/SKILL.md +455 -455
  134. package/vendor/Server/Skills/arkts-library-xts-code/assets/report-template.md +60 -60
  135. package/vendor/Server/Skills/arkts-library-xts-code/references/ACCESS_MODIFIER_PRECHECK.md +76 -76
  136. package/vendor/Server/Skills/arkts-library-xts-code/references/BUILD_VERIFICATION.md +200 -200
  137. package/vendor/Server/Skills/arkts-library-xts-code/references/CODE_STANDARDS.md +518 -518
  138. package/vendor/Server/Skills/arkts-library-xts-code/references/SUBAGENT_PROMPT_TEMPLATE.md +276 -276
  139. package/vendor/Server/Skills/arkts-library-xts-code/references/SYSTEM_API_LOOKUP.md +92 -92
  140. package/vendor/Server/Skills/arkts-library-xts-code/references/XTS_TEMPLATE.md +767 -767
  141. package/vendor/Server/Skills/arkts-library-xts-code/scripts/build.ps1 +93 -93
  142. package/vendor/Server/Skills/arkts-library-xts-code/scripts/code-linter.cjs +432 -432
  143. package/vendor/Server/Skills/arkts-library-xts-code/scripts/migrate.cjs +851 -851
  144. package/vendor/Server/Skills/arkts-library-xts-code/scripts/ohpm-switch.cjs +788 -788
  145. package/vendor/Server/Skills/arkts-library-xts-code/scripts/precheck-access-modifiers.cjs +221 -221
  146. package/vendor/Server/Skills/arkts-library-xts-code-fix/SKILL.md +228 -228
  147. package/vendor/Server/Skills/arkts-library-xts-coverage/SKILL.md +97 -97
  148. package/vendor/Server/Skills/arkts-library-xts-doc/SKILL.md +194 -194
  149. package/vendor/Server/Skills/arkts-library-xts-doc/references/EXAMPLES.md +575 -575
  150. package/vendor/Server/Skills/arkts-library-xts-doc/references/TEMPLATE.md +574 -574
  151. package/vendor/Server/Skills/arkts-library-xts-doc/references/TESTABILITY_GUIDE.md +182 -182
  152. package/vendor/Server/Skills/arkts-library-xts-execution/SKILL.md +490 -490
  153. package/vendor/Server/Skills/arkts-library-xts-execution/references/error-fixing-examples.md +217 -217
  154. package/vendor/Server/Skills/arkts-library-xts-execution/scripts/build.ps1 +93 -93
  155. package/vendor/Server/Skills/arkts-library-xts-execution/scripts/code-linter.cjs +432 -432
  156. package/vendor/Server/Skills/arkts-library-xts-execution/scripts/migrate.cjs +851 -851
  157. package/vendor/Server/Skills/arkts-library-xts-execution/scripts/ohpm-switch.cjs +788 -788
  158. package/vendor/Server/Skills/flutter-code-check/SKILL.md +299 -299
  159. package/vendor/Server/Skills/flutter-code-check/assets/OUTPUT_TEMPLATE.md +311 -311
  160. package/vendor/Server/Skills/flutter-code-check/references/CHECKLIST.md +190 -190
  161. package/vendor/Server/Skills/flutter-code-check/references/PLATFORMS.md +133 -133
  162. package/vendor/Server/Skills/flutter-code-check/scripts/validate-skill.cjs +259 -259
  163. package/vendor/Server/Skills/flutter-interface/README.md +75 -75
  164. package/vendor/Server/Skills/flutter-interface/SKILL.md +291 -291
  165. package/vendor/Server/Skills/flutter-interface/references/EXAMPLES.md +336 -336
  166. package/vendor/Server/Skills/flutter-interface/references/GIT_CLONE_GUIDE.md +190 -190
  167. package/vendor/Server/Skills/flutter-interface/references/SCRIPT_REFERENCE.md +258 -258
  168. package/vendor/Server/Skills/flutter-interface/references/TEMPLATE.md +254 -254
  169. package/vendor/Server/Skills/flutter-library-consistency-check/SKILL.md +517 -517
  170. package/vendor/Server/Skills/flutter-library-consistency-check/references/CHECKLIST.md +381 -381
  171. package/vendor/Server/Skills/flutter-library-consistency-check/references/FUNCTION_COMPARISON_GUIDE.md +341 -341
  172. package/vendor/Server/Skills/flutter-library-consistency-check/references/PLATFORM_LIMITATION_GUIDE.md +71 -71
  173. package/vendor/Server/Skills/flutter-library-documentation-quality-check/SKILL.md +518 -518
  174. package/vendor/Server/Skills/flutter-library-documentation-quality-check/references/CHECKLIST.md +406 -406
  175. package/vendor/Server/Skills/flutter-library-memory-leak-check/SKILL.md +652 -652
  176. package/vendor/Server/Skills/flutter-library-memory-leak-check/references/CHECKLIST.md +396 -396
  177. package/vendor/Server/Skills/flutter-library-stability-check/SKILL.md +744 -744
  178. package/vendor/Server/Skills/flutter-library-stability-check/references/CHECKLIST.md +720 -720
  179. package/vendor/Server/Skills/flutter-library-test-coverage-check/SKILL.md +414 -414
  180. package/vendor/Server/Skills/flutter-library-test-coverage-check/assets/REPORT_TEMPLATE.md +150 -150
  181. package/vendor/Server/Skills/flutter-library-test-coverage-check/references/CHECKLIST.md +304 -304
  182. package/vendor/Server/Skills/flutter-library-test-coverage-check/references/SCORING_RULES.md +265 -265
  183. package/vendor/Server/Skills/flutter-library-test-coverage-check/references/SCRIPT_USAGE.md +286 -286
  184. package/vendor/Server/Skills/knowledge-import/SKILL.md +383 -383
  185. package/vendor/Server/Skills/knowledge-import/references/MCP_TOOL_USAGE.md +165 -165
  186. package/vendor/Server/Skills/knowledge-import/references/SKILL_LOCATOR_GUIDE.md +91 -91
  187. package/vendor/Server/Skills/knowledge-import/references/SKILL_OPTIMIZATION_GUIDE.md +127 -127
  188. package/vendor/Server/Skills/ohos-lib-pr-push/SKILL.md +453 -301
  189. package/vendor/Server/Skills/ohos-lib-pr-push/assets/PR_CHECKLIST.md +151 -0
  190. package/vendor/Server/Skills/ohos-lib-pr-push/assets/PR_ISSUE_TEMPLATE.md +257 -105
  191. package/vendor/Server/Skills/ohos-lib-pr-push/references/GITCODE_API.md +191 -127
  192. package/vendor/Server/Skills/ohos-lib-pr-push/scripts/gitcode-api.cjs +317 -208
  193. package/vendor/Server/Skills/rn-code-check/SKILL.md +320 -320
  194. package/vendor/Server/Skills/rn-code-check/references/API-GUIDE.md +234 -234
  195. package/vendor/Server/Skills/rn-code-check/references/CHECKLIST.md +188 -188
  196. package/vendor/Server/Skills/rn-code-check/references/REPORT-TEMPLATE.md +246 -246
  197. package/vendor/Server/Skills/rn-lib-add-example/SKILL.md +198 -198
  198. package/vendor/Server/Skills/rn-lib-add-example/references/APP_RENAME_GUIDE.md +40 -40
  199. package/vendor/Server/Skills/rn-lib-add-example/references/SHELL_ADAPTATION_CHECKLIST.md +78 -78
  200. package/vendor/Server/Skills/rn-lib-add-example/references/TEMPLATE_REPO_GUIDE.md +70 -70
  201. package/vendor/Server/Skills/rn-lib-add-example/references/VERIFICATION_GUIDE.md +47 -47
  202. package/vendor/Server/Skills/rn-lib-add-example/scripts/create-example.cjs +252 -252
  203. package/vendor/Server/Skills/rn-lib-update-doc/SKILL.md +176 -176
  204. package/vendor/Server/Skills/rn-lib-update-doc/config.json +5 -5
  205. package/vendor/Server/Skills/rn-lib-update-doc/references/changelog-template.md +35 -35
  206. package/vendor/Server/Skills/rn-lib-update-doc/references/format-diff.md +137 -137
  207. package/vendor/Server/Skills/rn-lib-update-doc/references/link-registry.json +33 -33
  208. package/vendor/Server/Skills/rn-lib-update-doc/references/model-0.4.2-overview.md +40 -40
  209. package/vendor/Server/Skills/rn-lib-update-doc/references/model-0.4.2.md +429 -429
  210. package/vendor/Server/Skills/rn-lib-update-doc/scripts/detect-readme-format.cjs +105 -105
  211. package/vendor/Server/Skills/rn-lib-update-doc/scripts/find-usage-doc.cjs +90 -90
  212. package/vendor/Server/Skills/rn-lib-update-doc/scripts/link-verify.cjs +213 -213
  213. package/vendor/Server/Skills/rn-lib-update-doc/scripts/normalize-changelog.cjs +73 -73
  214. package/vendor/Server/Skills/rn-lib-update-doc/scripts/normalize-package-meta.cjs +258 -258
  215. package/vendor/Server/Skills/rn-lib-update-doc/scripts/normalize-readme-links.cjs +341 -341
  216. package/vendor/Server/Skills/rn-lib-update-doc/scripts/resolve-usage-docs.cjs +67 -67
  217. package/vendor/Server/Skills/rn-library-consistency-check/SKILL.md +299 -299
  218. package/vendor/Server/Skills/rn-library-consistency-check/assets/REPORT_TEMPLATE.md +293 -293
  219. package/vendor/Server/Skills/rn-library-consistency-check/references/ARKTS_COMPONENT_CHECK_GUIDE.md +857 -857
  220. package/vendor/Server/Skills/rn-library-consistency-check/references/ARKTS_TURBOMODULE_CHECK_GUIDE.md +856 -856
  221. package/vendor/Server/Skills/rn-library-consistency-check/references/CHECKLIST.md +157 -157
  222. package/vendor/Server/Skills/rn-library-consistency-check/references/CPP_COMPONENT_CHECK_GUIDE.md +801 -801
  223. package/vendor/Server/Skills/rn-library-consistency-check/references/CPP_TURBOMODULE_CHECK_GUIDE.md +683 -683
  224. package/vendor/Server/Skills/rn-library-consistency-check/references/FUNCTION_COMPARISON_GUIDE.md +233 -233
  225. package/vendor/Server/Skills/rn-library-consistency-check/references/NATIVE_COMPONENT_IDENTIFICATION.md +1373 -1373
  226. package/vendor/Server/Skills/rn-library-consistency-check/references/SCORING_STANDARD.md +220 -220
  227. package/vendor/Server/Skills/rn-library-consistency-check/scripts/validate-skill.py +245 -245
  228. package/vendor/Server/Skills/rn-library-documentation-quality-check/SKILL.md +263 -263
  229. package/vendor/Server/Skills/rn-library-documentation-quality-check/assets/MODEL.md +414 -414
  230. package/vendor/Server/Skills/rn-library-documentation-quality-check/assets/MODEL_en.md +413 -413
  231. package/vendor/Server/Skills/rn-library-documentation-quality-check/assets/REPORT_TEMPLATE.md +311 -311
  232. package/vendor/Server/Skills/rn-library-documentation-quality-check/references/CHECK_CHECKLIST.md +355 -355
  233. package/vendor/Server/Skills/rn-library-documentation-quality-check/references/CROSS_VALIDATION_GUIDE.md +115 -115
  234. package/vendor/Server/Skills/rn-library-documentation-quality-check/references/DOC_COLLECTION_GUIDE.md +139 -139
  235. package/vendor/Server/Skills/rn-library-documentation-quality-check/references/EXAMPLES.md +586 -586
  236. package/vendor/Server/Skills/rn-library-documentation-quality-check/references/GOTCHAS.md +57 -57
  237. package/vendor/Server/Skills/rn-library-documentation-quality-check/scripts/validate-skill.py +259 -259
  238. package/vendor/Server/Skills/rn-library-interface-analyzer/SKILL.md +301 -301
  239. package/vendor/Server/Skills/rn-library-interface-analyzer/assets/REPORT_TEMPLATE.md +148 -148
  240. package/vendor/Server/Skills/rn-library-interface-analyzer/references/DOC_ANNOTATION_GUIDE.md +118 -118
  241. package/vendor/Server/Skills/rn-library-interface-analyzer/references/SCRIPT_USAGE_GUIDE.md +168 -168
  242. package/vendor/Server/Skills/rn-library-interface-analyzer/references/URL_PARSING_RULES.md +176 -176
  243. package/vendor/Server/Skills/rn-library-issue-generalizer/SKILL.md +274 -274
  244. package/vendor/Server/Skills/rn-library-issue-generalizer/assets/case-template.md +45 -45
  245. package/vendor/Server/Skills/rn-library-issue-generalizer/assets/rule-template.md +25 -25
  246. package/vendor/Server/Skills/rn-library-issue-generalizer/references/PR_FETCH_GUIDE.md +61 -61
  247. package/vendor/Server/Skills/rn-library-issue-generalizer/references/RULE_TEMPLATE_GUIDE.md +165 -165
  248. package/vendor/Server/Skills/rn-library-issue-generalizer/scripts/fetch-pr-info.py +239 -239
  249. package/vendor/Server/Skills/rn-library-known-issue-check/SKILL.md +263 -263
  250. package/vendor/Server/Skills/rn-library-known-issue-check/assets/REPORT_TEMPLATE.md +96 -96
  251. package/vendor/Server/Skills/rn-library-known-issue-check/references/COMPLIANCE_CHECKLIST.md +57 -57
  252. package/vendor/Server/Skills/rn-library-known-issue-check/references/INSPECTION_GUIDE.md +99 -99
  253. package/vendor/Server/Skills/rn-library-known-issue-check/references/PRESCREEN_GUIDE.md +64 -64
  254. package/vendor/Server/Skills/rn-library-known-issue-check/references/SOURCE_PREP_GUIDE.md +74 -74
  255. package/vendor/Server/Skills/rn-library-known-issue-check/scripts/validate-known-issue-report.py +164 -164
  256. package/vendor/Server/Skills/rn-library-memory-leak-check/SKILL.md +356 -339
  257. package/vendor/Server/Skills/rn-library-memory-leak-check/assets/REPORT_TEMPLATE.md +85 -83
  258. package/vendor/Server/Skills/rn-library-memory-leak-check/references/CHECKLIST.md +905 -859
  259. package/vendor/Server/Skills/rn-library-memory-leak-check/references/HARMONY_CODE_PATTERNS.md +124 -122
  260. package/vendor/Server/Skills/rn-library-memory-leak-check/scripts/scan_harmony_code.py +287 -279
  261. package/vendor/Server/Skills/rn-library-stability-check/SKILL.md +273 -273
  262. package/vendor/Server/Skills/rn-library-stability-check/assets/REPORT_TEMPLATE.md +129 -129
  263. package/vendor/Server/Skills/rn-library-stability-check/references/CHECKLIST.md +581 -581
  264. package/vendor/Server/Skills/rn-library-stability-check/references/SCORING_RULES.md +138 -138
  265. package/vendor/Server/Skills/rn-library-stability-check/scripts/scan-harmony-code.py +168 -168
  266. package/vendor/Server/Skills/rn-library-test-coverage-check/SKILL.md +526 -506
  267. package/vendor/Server/Skills/rn-library-test-coverage-check/references/CHECKLIST.md +321 -280
  268. package/vendor/Server/Skills/rn-library-test-coverage-check/references/DEMO_LOCATION.md +221 -221
  269. package/vendor/Server/Skills/rn-library-test-coverage-check/references/REPORT_TEMPLATE.md +220 -209
  270. package/vendor/Server/Skills/rn-library-test-coverage-check/references/SCRIPT_USAGE.md +325 -325
  271. package/vendor/Server/Skills/rn-ohos-xts-generator/SKILL.md +279 -279
  272. package/vendor/Server/Skills/rn-ohos-xts-generator/assets/TEST_FILE_TEMPLATES.md +404 -404
  273. package/vendor/Server/Skills/rn-ohos-xts-generator/references/BUILD_TROUBLESHOOTING.md +185 -185
  274. package/vendor/Server/Skills/rn-ohos-xts-generator/references/BUILD_WORKFLOW.md +265 -265
  275. package/vendor/Server/Skills/rn-ohos-xts-generator/references/COMMON_CONFIG.md +189 -189
  276. package/vendor/Server/Skills/rn-ohos-xts-generator/references/CONFIG_GUIDE.md +203 -203
  277. package/vendor/Server/Skills/rn-ohos-xts-generator/references/COVERAGE_ANALYZER.md +147 -147
  278. package/vendor/Server/Skills/rn-ohos-xts-generator/references/DESIGN_DOC_GUIDE.md +148 -148
  279. package/vendor/Server/Skills/rn-ohos-xts-generator/references/GENERATION_FLOW.md +165 -165
  280. package/vendor/Server/Skills/rn-ohos-xts-generator/references/GENERATION_STRATEGY.md +195 -195
  281. package/vendor/Server/Skills/rn-ohos-xts-generator/references/HARMONY_SCANNER.md +249 -249
  282. package/vendor/Server/Skills/rn-ohos-xts-generator/references/JEST_FRAMEWORK.md +235 -235
  283. package/vendor/Server/Skills/rn-ohos-xts-generator/references/PLATFORM_COMPAT.md +161 -161
  284. package/vendor/Server/Skills/rn-ohos-xts-generator/references/RN_TEST_PATTERNS.md +413 -413
  285. package/vendor/Server/Skills/rn-ohos-xts-generator/references/SOURCE_PARSER.md +235 -235
  286. package/vendor/Server/Skills/rn-ohos-xts-generator/references/TEST_CONVENTIONS.md +245 -245
  287. package/vendor/Server/Skills/rn-ohos-xts-generator/references/TROUBLESHOOTING.md +282 -282
  288. package/vendor/Server/Skills/rn-ohos-xts-generator/references/USAGE_GUIDE.md +251 -251
  289. package/vendor/Server/Skills/rnoh-cptf-api-test/SKILL.md +469 -469
  290. package/vendor/Server/Skills/rnoh-cptf-api-test/assets/CPTF_TEST_TEMPLATE.md +310 -310
  291. package/vendor/Server/Skills/rnoh-cptf-api-test/assets/DEMO_PAGE_TEMPLATE.md +303 -303
  292. package/vendor/Server/Skills/rnoh-cptf-api-test/references/CPTF_TEST_GENERATION_GUIDE.md +421 -421
  293. package/vendor/Server/Skills/rnoh-cptf-api-test/references/DEMO_GENERATION_GUIDE.md +358 -358
  294. package/vendor/Server/Skills/rnoh-cptf-api-test/references/QUALITY_CHECKLIST.md +114 -114
  295. package/vendor/Server/Skills/rnoh-cptf-api-test/references/SPEC_PARSING_GUIDE.md +274 -274
  296. package/vendor/Server/Skills/rnoh-cptf-api-test/scripts/validate-rnoh-cptf-api-test.sh +185 -185
  297. package/vendor/Server/Skills/rnoh-cptf-demo-test/SKILL.md +191 -191
  298. package/vendor/Server/Skills/rnoh-cptf-demo-test/assets/TEST_TEMPLATE.md +216 -216
  299. package/vendor/Server/Skills/rnoh-cptf-demo-test/references/CPTF_SETUP.md +107 -107
  300. package/vendor/Server/Skills/rnoh-cptf-demo-test/references/DEMO_ANALYSIS.md +120 -120
  301. package/vendor/Server/Skills/rnoh-cptf-demo-test/scripts/validate-rnoh-cptf-test.sh +108 -108
  302. package/vendor/Server/Skills/rnoh-cptf-test-run/SKILL.md +237 -237
  303. package/vendor/Server/Skills/rnoh-cptf-test-run/references/DEBUG_PLAYBOOK.md +261 -261
  304. package/vendor/Server/Skills/rnoh-cptf-test-run/scripts/validate-run-env.sh +112 -112
  305. package/vendor/Server/Skills/rnoh-lib-code-adapt/SKILL.md +497 -497
  306. package/vendor/Server/Skills/rnoh-lib-code-adapt/references/adapt-checklist.md +270 -270
  307. package/vendor/Server/Skills/rnoh-lib-code-adapt/references/api-migration-checklist.md +164 -164
  308. package/vendor/Server/Skills/rnoh-lib-code-adapt/references/common-compile-errors.md +556 -556
  309. package/vendor/Server/Skills/rnoh-lib-code-adapt/references/deveco-cli-tools.md +459 -459
  310. package/vendor/Server/Skills/rnoh-lib-code-adapt/references/edge-cases.md +65 -65
  311. package/vendor/Server/Skills/rnoh-lib-code-adapt/references/har-tgz-build.md +512 -512
  312. package/vendor/Server/Skills/rnoh-lib-code-adapt/references/version-compat-matrix.md +132 -132
  313. package/vendor/Server/Skills/rnoh-lib-code-adapt/scripts/auto-integrate.sh +424 -424
  314. package/vendor/Server/Skills/rnoh-lib-demo-coverage/SKILL.md +137 -137
  315. package/vendor/Server/Skills/rnoh-lib-demo-create/SKILL.md +306 -306
  316. package/vendor/Server/Skills/rnoh-lib-demo-create/assets/DEMO_PAGE_TEMPLATE.md +485 -485
  317. package/vendor/Server/Skills/rnoh-lib-demo-create/assets/INDEX_TEMPLATE.md +172 -172
  318. package/vendor/Server/Skills/rnoh-lib-demo-create/references/DEMO_CODE_RULES.md +422 -422
  319. package/vendor/Server/Skills/rnoh-lib-demo-create/references/QUALITY_CHECKLIST.md +95 -95
  320. package/vendor/Server/Skills/rnoh-lib-demo-create/references/SOURCE_EXPLORATION.md +169 -169
  321. package/vendor/Server/Skills/rnoh-lib-demo-doc/SKILL.md +554 -554
  322. package/vendor/Server/Skills/rnoh-lib-demo-doc/assets/DEMO_DOC_TEMPLATE.md +106 -106
  323. package/vendor/Server/Skills/rnoh-lib-demo-doc/references/COVERAGE_INCREMENTAL.md +216 -216
  324. package/vendor/Server/Skills/rnoh-lib-demo-doc/references/DEMO_DESIGN_RULES.md +413 -413
  325. package/vendor/Server/Skills/rnoh-lib-demo-doc/references/OUTPUT_FORMAT.md +269 -269
  326. package/vendor/Server/Skills/rnoh-lib-demo-doc/references/QUALITY_CHECKLIST.md +98 -98
  327. package/vendor/Server/Skills/rnoh-lib-func-test/SKILL.md +337 -337
  328. package/vendor/Server/Skills/rnoh-lib-rnt-search/SKILL.md +722 -722
  329. package/vendor/Server/Skills/rnoh-lib-rnt-search/assets/PORT_REPORT_TEMPLATE.md +219 -219
  330. package/vendor/Server/Skills/rnoh-lib-rnt-search/references/DEMO_PORTING_GUIDE.md +345 -345
  331. package/vendor/Server/Skills/rnoh-lib-rnt-search/references/PORT_VERIFICATION.md +269 -269
  332. package/vendor/Server/Skills/rnoh-lib-rnt-search/references/RNT_PROJECT_STRUCTURE.md +173 -173
  333. package/vendor/Server/Skills/rnoh-lib-rnt-search/scripts/analyze-demo-deps.cjs +267 -267
  334. package/vendor/Server/Skills/rnoh-lib-rnt-search/scripts/detect-rnoh-version.cjs +174 -174
  335. package/vendor/Server/Skills/rnoh-lib-rnt-search/scripts/find-rnt-demo.sh +111 -111
  336. package/vendor/Server/Skills/rnoh-lib-rnt-search/scripts/port-demo.sh +186 -186
  337. package/vendor/Server/Skills/rnoh-lib-rnt-search/scripts/read-lib-meta.cjs +150 -150
  338. package/vendor/Server/Skills/rnoh-lib-test-align/SKILL.md +296 -296
  339. package/vendor/Server/Skills/rnoh-lib-test-align/references/jest-troubleshooting.md +371 -371
  340. package/vendor/Server/Skills/rnoh-lib-test-align/references/port-test-demo-guide.md +212 -212
  341. package/vendor/Server/Skills/rnoh-lib-test-align/references/runtime-troubleshooting.md +345 -345
  342. package/vendor/Server/Skills/rnoh-lib-test-align/references/unit-test-template.md +223 -223
  343. package/vendor/Server/Skills/rnoh-lib-test-align/scripts/port-test-demo.sh +1053 -1053
  344. package/vendor/Server/Skills/rnoh-lib-version-diff/SKILL.md +295 -255
  345. package/vendor/Server/Skills/rnoh-lib-version-diff/references/OHOS_ADAPTATION.md +82 -77
  346. package/vendor/Server/Skills/rnoh-lib-version-diff/references/OHOS_API_LOOKUP.md +224 -224
  347. package/vendor/Server/Skills/rnoh-lib-version-diff/references/script-reference.md +175 -174
  348. package/vendor/Server/Skills/rnoh-lib-version-diff/scripts/detect-upstream-version.js +967 -967
  349. package/vendor/Server/Skills/rnoh-lib-version-diff/scripts/generate-diff.js +1798 -1798
  350. package/vendor/Server/Skills/rnoh-lib-version-diff/scripts/package.json +5 -5
  351. package/vendor/Server/Skills/rnoh-lib-version-diff/templates/report-template.md +180 -137
  352. package/vendor/Server/Skills/rnoh-lib-xts-coverage/SKILL.md +145 -145
  353. package/vendor/Server/Skills/skill-generator/SKILL.md +365 -365
  354. package/vendor/Server/Skills/skill-generator/assets/SKILL_TEMPLATE.md +183 -183
  355. package/vendor/Server/Skills/skill-generator/references/COMPLIANCE_CHECKLIST.md +82 -82
  356. package/vendor/Server/Skills/skill-generator/references/KB_QUERY_GUIDE.md +110 -110
  357. package/vendor/Server/Skills/skill-generator/references/MCP_TOOL_DISCOVERY.md +122 -122
  358. package/vendor/Server/Skills/skill-generator/references/SKILL_SPEC.md +121 -121
  359. package/vendor/Server/Skills/skill-generator/scripts/validate-skill.cjs +259 -259
  360. package/vendor/Server/Skills/task-analysis/SKILL.md +234 -234
  361. package/vendor/Server/Skills/task-analysis/assets/OUTPUT_EXAMPLES.md +160 -160
  362. package/vendor/Server/Skills/task-analysis/references/ACCEPTANCE_CRITERIA_GUIDE.md +190 -190
  363. package/vendor/Server/Skills/task-analysis/references/ANALYSIS_SCHEMA.md +224 -224
  364. package/vendor/Server/Skills/task-analysis/references/INTEGRATION_CONTEXT.md +162 -162
  365. package/vendor/Server/Skills/task-analysis/references/LESSONS_LEARNED.md +55 -55
  366. package/vendor/Server/Skills/task-analysis/references/REPLAN_CONTEXT_GUIDE.md +126 -126
  367. package/vendor/Server/Skills/task-analysis/scripts/validate-analysis-output.sh +256 -256
  368. package/vendor/Server/Skills/task-decomposition/SKILL.md +528 -528
  369. package/vendor/Server/Skills/task-decomposition/assets/OUTPUT_EXAMPLES.md +451 -451
  370. package/vendor/Server/Skills/task-decomposition/references/ACCEPTANCE_CRITERIA_GUIDE.md +179 -179
  371. package/vendor/Server/Skills/task-decomposition/references/DEPENDENCY_GRAPH_GUIDE.md +152 -152
  372. package/vendor/Server/Skills/task-decomposition/references/INTEGRATION_CONTEXT.md +175 -175
  373. package/vendor/Server/Skills/task-decomposition/references/REPLAN_CONTEXT_GUIDE.md +195 -195
  374. package/vendor/Server/Skills/task-decomposition/references/TASK_DECOMPOSITION_TEMPLATES.md +226 -226
  375. package/vendor/Server/Skills/task-decomposition/scripts/list-available-skills.cjs +263 -263
  376. package/vendor/Server/Skills/task-decomposition/scripts/skill-blacklist.json +29 -29
  377. package/vendor/Server/Skills/task-decomposition/scripts/validate-decomposition-output.cjs +376 -376
  378. package/vendor/Server/Skills/task-decomposition/scripts/validate-param-consistency.cjs +259 -259
  379. package/vendor/Server/Skills/task-decomposition/scripts/validate-specified-skills.cjs +216 -216
  380. package/vendor/Server/Skills/task-decomposition/scripts/validate-step-graph.cjs +424 -424
  381. package/vendor/Server/Skills/tpc-cpp-check/SKILL.md +427 -427
  382. package/vendor/Server/Skills/tpc-cpp-check/references/CHECKLIST.md +214 -214
  383. package/vendor/Server/Skills/tpc-cpp-check/references/HPKBUILD_GUIDE.md +235 -235
  384. package/vendor/Server/Skills/tpc-cpp-check/references/REPORT_TEMPLATE.md +250 -250
  385. package/vendor/Server/TestCheck/TestCheck.js +775 -775
  386. package/vendor/Server/TestCheck/index.js +11 -11
  387. package/vendor/Server/library/McpToolGuide.js +280 -280
  388. package/vendor/Server/library/StatsService.js +126 -126
  389. package/vendor/Server/library/cstTime.js +16 -16
  390. package/vendor/VERSION +3 -3
  391. package/workflows/pr-check.mjs +105 -105
  392. package/workflows/pr-push.mjs +192 -192
  393. package/vendor/Server/Agent/SkillSelector/types.js +0 -63
@@ -1,1133 +1,1133 @@
1
- /**
2
- * LoopEngine —— 大循环协调引擎(Loop Engineering)
3
- *
4
- * 职责:
5
- * - 协调 Brain → Orchestrator → FlexRunner → TestCheck 的完整闭环
6
- * - Orchestrator 执行完成后,调用 TestCheck 进行准出审核
7
- * - 审核未通过时,调用 Brain.replanTask() 重新生成目标,进入下一轮循环
8
- * - 循环终止条件:准出通过 OR 达到最大循环次数
9
- * - 全流程状态持久化,支持中断恢复
10
- * - 通过 SSE 实时推送大循环进度
11
- *
12
- * 架构角色(Loop Engineering 大循环):
13
- *
14
- * ┌──────────────────────────────────────────────────────────────┐
15
- * │ LoopEngine │
16
- * │ │
17
- * │ ┌─────────┐ ┌──────────────┐ ┌──────────┐ │
18
- * │ │ Brain │───→│ Orchestrator │───→│ TestCheck│ │
19
- * │ │ 拆解/重规│ │ 编排执行 │ │ 准出审核 │ │
20
- * │ └────▲────┘ └──────────────┘ └────┬─────┘ │
21
- * │ │ │ │
22
- * │ └────────── 未通过 ────────────────┘ │
23
- * │ │
24
- * │ 循环终止:准出通过 OR maxLoops │
25
- * └──────────────────────────────────────────────────────────────┘
26
- *
27
- * 设计原则:
28
- * - 高内聚:大循环协调逻辑集中于此,不感知各子模块的内部实现
29
- * - 低耦合:通过依赖注入 Brain / OrchestratorController / TestCheck
30
- * - 单一职责:LoopEngine 只管循环驱动和状态流转,不关心拆解/编排/审核细节
31
- * - 可恢复:循环状态持久化,服务重启后可恢复循环上下文
32
- */
33
-
34
- import path from 'node:path';
35
- import { fileURLToPath } from 'node:url';
36
-
37
- import { Brain, BrainStatus } from '../Brain/index.js';
38
- import { TestCheck, CheckStatus } from '../TestCheck/index.js';
39
- import { nowCST } from '../library/cstTime.js';
40
- import { createStorage } from '../DbUse/index.js';
41
-
42
- // ─── 常量 ────────────────────────────────────────────────────────────────────
43
-
44
- /** LoopEngine 循环状态 */
45
- export const LoopStatus = Object.freeze({
46
- IDLE: 'idle', // 未启动
47
- PLANNING: 'planning', // Brain 拆解/重规划中
48
- EXECUTING: 'executing', // Orchestrator 执行中
49
- CHECKING: 'checking', // TestCheck 准出审核中
50
- ABORTING: 'aborting', // 终止已请求,等待各阶段轮询退出(过渡态)
51
- PASSED: 'passed', // 准出通过,循环结束
52
- FAILED: 'failed', // 达到最大循环次数仍未通过
53
- ABORTED: 'aborted', // 被手动终止
54
- ERROR: 'error' // 循环过程出错
55
- });
56
-
57
- /** SSE 事件类型 */
58
- const SSE_EVENT = Object.freeze({
59
- LOOP_START: 'loop:start',
60
- LOOP_ROUND_START: 'loop:round:start',
61
- LOOP_PLANNING: 'loop:planning',
62
- LOOP_EXECUTING: 'loop:executing',
63
- LOOP_CHECKING: 'loop:checking',
64
- LOOP_ROUND_END: 'loop:round:end',
65
- LOOP_COMPLETE: 'loop:complete',
66
- LOOP_ERROR: 'loop:error',
67
- LOOP_ABORTING: 'loop:aborting',
68
- LOOP_ABORTED: 'loop:aborted'
69
- });
70
-
71
- /** 默认最大循环次数 */
72
- const DEFAULT_MAX_LOOPS = 3;
73
-
74
- /**
75
- * ABORTED_ERROR —— 大循环终止专用错误。
76
- *
77
- * 由各阶段(Brain 拆解 / Orchestrator 等待 / TestCheck 审核)检测到终止请求后抛出,
78
- * startLoop 的 catch 分支识别本错误后立即跳出循环(不再进入后续阶段),
79
- * 区别于普通的轮次执行异常(后者会继续下一轮)。
80
- */
81
- class ABORTED_ERROR extends Error {
82
- /**
83
- * @param {string} [message]
84
- */
85
- constructor(message = '大循环已终止') {
86
- super(message);
87
- this.name = 'AbortError';
88
- }
89
- }
90
-
91
- // ─── LoopEngine 类 ───────────────────────────────────────────────────────────
92
-
93
- /**
94
- * LoopEngine —— 大循环协调引擎
95
- */
96
- class LoopEngine {
97
- /**
98
- * @param {object} deps - 依赖注入
99
- * @param {object} deps.brainController - BrainController 实例(必须)
100
- * @param {object} deps.orchestratorController - OrchestratorController 实例(必须)
101
- * @param {TestCheck} [deps.testCheck] - TestCheck 实例(可选,自动创建)
102
- * @param {object} [deps.storage] - 存储门面
103
- * @param {object} [deps.sseHub] - SseHub 实例
104
- * @param {string} [deps.skillDirectory] - SKILL 目录路径
105
- */
106
- constructor(deps = {}) {
107
- if (!deps.brainController) {
108
- throw new Error('LoopEngine: brainController 为必填依赖');
109
- }
110
- if (!deps.orchestratorController) {
111
- throw new Error('LoopEngine: orchestratorController 为必填依赖');
112
- }
113
-
114
- this.brainController = deps.brainController;
115
- this.orchestratorController = deps.orchestratorController;
116
- this.storage = deps.storage || createStorage();
117
- this.sseHub = deps.sseHub || null;
118
- this.skillDirectory = deps.skillDirectory || null;
119
-
120
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
121
- this.skillDirectory = deps.skillDirectory ||
122
- path.resolve(__dirname, '..', 'Skills');
123
-
124
- // TestCheck 实例(懒初始化)
125
- this._testCheck = deps.testCheck || null;
126
-
127
- /**
128
- * AbortState —— 大循环终止状态(由 abortLoop 驱动,各阶段轮询检查)。
129
- *
130
- * 场景:setAbortFlag 置位后,正在运行的 Brain/TestCheck SKILL 轮询
131
- * 需要在下一个轮询周期感知并退出;同时记录活跃 sessionId 供 abortLoop
132
- * 远程中止 opencode session(否则 session 会在后台继续跑到自然完成)。
133
- */
134
- this._abortStates = new Map();
135
-
136
- /** @type {Map<string, LoopContext>} key = taskId */
137
- this._loops = new Map();
138
- }
139
-
140
- // ─── 属性访问 ──────────────────────────────────────────────────────
141
-
142
- /**
143
- * 获取 TestCheck 实例(懒初始化)。
144
- * @returns {TestCheck}
145
- * @private
146
- */
147
- _getTestCheck() {
148
- if (!this._testCheck) {
149
- this._testCheck = new TestCheck({
150
- storage: this.storage,
151
- sseHub: this.sseHub,
152
- skillDirectory: this.skillDirectory
153
- });
154
- }
155
- return this._testCheck;
156
- }
157
-
158
- /**
159
- * 获取循环上下文。
160
- * @param {string} taskId
161
- * @returns {LoopContext|undefined}
162
- */
163
- getLoop(taskId) {
164
- return this._loops.get(taskId);
165
- }
166
-
167
- /**
168
- * 获取所有循环。
169
- * @returns {LoopContext[]}
170
- */
171
- getLoops() {
172
- return Array.from(this._loops.values());
173
- }
174
-
175
- // ─── 终止状态管理 ──────────────────────────────────────────────────
176
-
177
- /**
178
- * 获取(或惰性创建)任务的 AbortState。
179
- * @param {string} taskId
180
- * @returns {{ aborted: boolean, sessionId: string|null }}
181
- * @private
182
- */
183
- _getAbortState(taskId) {
184
- let st = this._abortStates.get(taskId);
185
- if (!st) {
186
- st = { aborted: false, sessionId: null };
187
- this._abortStates.set(taskId, st);
188
- }
189
- return st;
190
- }
191
-
192
- /**
193
- * 判断任务是否已被请求终止。
194
- * @param {string} taskId
195
- * @returns {boolean}
196
- */
197
- isAborted(taskId) {
198
- const st = this._abortStates.get(taskId);
199
- return Boolean(st && st.aborted);
200
- }
201
-
202
- /**
203
- * 登记当前阶段正在运行的 opencode sessionId(Brain 拆解 / TestCheck 审核)。
204
- *
205
- * 供 abortLoop 远程中止 session;sessionId 更新时若已请求终止则立即中止新 session。
206
- *
207
- * @param {string} taskId
208
- * @param {string|null} sessionId - null 表示清除
209
- * @private
210
- */
211
- _trackSession(taskId, sessionId) {
212
- const st = this._getAbortState(taskId);
213
-
214
- // 终止已请求:新登记的 session 直接远程中止,防止 Brain/审核
215
- // 轮询间隙里新建的 session 继续在后台运行
216
- if (st.aborted && sessionId) {
217
- this._abortSessions([sessionId]);
218
- }
219
-
220
- if (sessionId) {
221
- st.pendingSessionId = sessionId; // 尚未被消费的 session
222
- delete st.abortedSessionIds?.[sessionId];
223
- } else {
224
- st.pendingSessionId = null;
225
- }
226
- }
227
-
228
- /**
229
- * 消费未中止的 pending session(轮询线程检测到终止时调用,返回应立即远程中止的 sessionId)。
230
- * @param {string} taskId
231
- * @returns {string|null}
232
- * @private
233
- */
234
- _consumePendingSession(taskId) {
235
- const st = this._abortStates.get(taskId);
236
- if (!st || !st.pendingSessionId) return null;
237
- const sid = st.pendingSessionId;
238
- st.pendingSessionId = null;
239
- return sid;
240
- }
241
-
242
- /**
243
- * 远程中止 opencode sessions(尽力而为,失败仅记日志)。
244
- * @param {string[]} sessionIds
245
- * @returns {Promise<void>}
246
- * @private
247
- */
248
- async _abortSessions(sessionIds) {
249
- if (!sessionIds || sessionIds.length === 0) return;
250
-
251
- let cli = null;
252
- try {
253
- const mod = await import('../CLI/cli.js');
254
- cli = new mod.CLIController();
255
- } catch (err) {
256
- this._log('warn', `_abortSessions: 加载 CLIController 失败: ${err.message}`);
257
- return;
258
- }
259
-
260
- for (const sid of sessionIds) {
261
- if (!sid) continue;
262
- try {
263
- await cli.abortSession(sid);
264
- this._log('info', `_abortSessions: 已远程中止 session ${sid}`);
265
- } catch (err) {
266
- // session 可能已自然结束,中止失败不阻断终止流程
267
- this._log('warn', `_abortSessions: 中止 session ${sid} 失败: ${err.message}`);
268
- }
269
- }
270
- }
271
-
272
- // ─── SSE 推送 ──────────────────────────────────────────────────────
273
-
274
- /**
275
- * 推送循环进度事件。
276
- * @param {string} channel
277
- * @param {string} eventType
278
- * @param {object} data
279
- * @private
280
- */
281
- _emit(channel, eventType, data) {
282
- if (this.sseHub) {
283
- this.sseHub.emit(channel, eventType, data);
284
- }
285
- }
286
-
287
- // ─── 核心方法 ──────────────────────────────────────────────────────
288
-
289
- /**
290
- * 启动大循环 — 从自然语言任务描述开始,执行完整的拆解→编排→审核闭环。
291
- *
292
- * 流程:
293
- * 1. Brain.planTask() 拆解任务
294
- * 2. Orchestrator 加载并执行
295
- * 3. TestCheck.runCheck() 准出审核
296
- * 4. 未通过 → Brain.replanTask() 重新生成目标 → 回到步骤 2
297
- * 5. 循环终止:准出通过 OR 达到最大循环次数
298
- *
299
- * @param {object} params
300
- * @param {string} params.taskDescription - 自然语言任务描述(必填)
301
- * @param {string} [params.taskId] - 指定 taskId
302
- * @param {string} [params.taskDir] - 指定工作目录
303
- * @param {string} [params.skillDirectory] - 覆盖 SKILL 目录
304
- * @param {string} [params.acceptanceCriteria] - 用户提供的验收标准
305
- * @param {number} [params.maxLoops] - 最大循环次数(默认 3)
306
- * @returns {Promise<LoopResult>}
307
- */
308
- async startLoop(params = {}) {
309
- const { taskDescription } = params;
310
-
311
- if (!taskDescription || typeof taskDescription !== 'string') {
312
- throw new Error('LoopEngine.startLoop: taskDescription 不能为空');
313
- }
314
-
315
- const maxLoops = typeof params.maxLoops === 'number' && params.maxLoops > 0
316
- ? params.maxLoops
317
- : DEFAULT_MAX_LOOPS;
318
-
319
- // 生成 taskId(如果未指定)
320
- const taskId = params.taskId || _generateTaskId();
321
- const skillDir = params.skillDirectory || this.skillDirectory;
322
-
323
- // 解析 taskDir:与 JSON 状态文件(storage.json('task'))保持同一目录 db/task/{taskId}
324
- const taskDir = params.taskDir ||
325
- path.resolve(this.storage.manager.rootDir, 'task', taskId);
326
-
327
- const channel = taskId;
328
- const startTime = nowCST();
329
-
330
- /** @type {LoopContext} */
331
- const ctx = {
332
- taskId,
333
- taskDescription,
334
- userAcceptanceCriteria: params.acceptanceCriteria || '',
335
- taskDir,
336
- skillDirectory: skillDir,
337
- maxLoops,
338
- currentLoop: 0,
339
- status: LoopStatus.IDLE,
340
- startTime,
341
- endTime: null,
342
- rounds: [],
343
- error: null,
344
- aborted: false
345
- };
346
-
347
- this._loops.set(taskId, ctx);
348
-
349
- // 重置终止状态(同 taskId 重新启动场景)
350
- const abortState = this._getAbortState(taskId);
351
- abortState.aborted = false;
352
- abortState.pendingSessionId = null;
353
-
354
- // 立即持久化 loop.json,确保大循环启动后任务即可在列表中可见
355
- // 防止系统重启时因 loop.json 未写入导致任务丢失
356
- await this._persistLoopState(ctx);
357
-
358
- this._emit(channel, SSE_EVENT.LOOP_START, {
359
- taskId, maxLoops, taskDescription: taskDescription.slice(0, 200), startTime
360
- });
361
-
362
- try {
363
- // ─── 大循环主体 ───
364
- while (ctx.currentLoop < ctx.maxLoops && !ctx.aborted) {
365
- const loopNum = ctx.currentLoop;
366
-
367
- this._log('info', `═══ 大循环第 ${loopNum + 1}/${ctx.maxLoops} 轮开始 ═══`);
368
- this._emit(channel, SSE_EVENT.LOOP_ROUND_START, {
369
- taskId, loop: loopNum, total: ctx.maxLoops
370
- });
371
-
372
- /** @type {RoundResult} */
373
- const round = {
374
- loop: loopNum,
375
- startTime: nowCST(),
376
- endTime: null,
377
- planningResult: null,
378
- orchestratorResult: null,
379
- checkResult: null,
380
- passed: false,
381
- error: null
382
- };
383
-
384
- // 立即将 round 加入 ctx.rounds,使前端轮询能实时看到各阶段进度
385
- ctx.rounds.push(round);
386
-
387
- try {
388
- // ── 阶段 1:Brain 拆解 / 重规划 ──
389
- ctx.status = LoopStatus.PLANNING;
390
- await this._persistLoopState(ctx);
391
- this._emit(channel, SSE_EVENT.LOOP_PLANNING, {
392
- taskId, loop: loopNum, isReplan: loopNum > 0
393
- });
394
-
395
- const planningResult = await this._runBrain(ctx, params);
396
- round.planningResult = planningResult;
397
-
398
- // ── 终止检查点:Brain 拆解期间收到终止请求 → 不进入编排阶段 ──
399
- // (Brain 内部的 SKILL 轮询会因 abort 提前退出并抛 ABORTED 错误,
400
- // 此检查兜底处理 Brain 自然完成但终止已请求的场景)
401
- if (this.isAborted(taskId)) {
402
- ctx.aborted = true;
403
- throw new ABORTED_ERROR('Brain 拆解完成前收到终止请求');
404
- }
405
-
406
- // ── 阶段 2:Orchestrator 编排执行 ──
407
- ctx.status = LoopStatus.EXECUTING;
408
- await this._persistLoopState(ctx);
409
- this._emit(channel, SSE_EVENT.LOOP_EXECUTING, {
410
- taskId, loop: loopNum
411
- });
412
-
413
- const orchestratorResult = await this._runOrchestrator(ctx, planningResult);
414
- round.orchestratorResult = orchestratorResult;
415
-
416
- // ── 终止检查点:编排期间收到终止请求(waitFor 检测到后提前返回)→ 不进入审核阶段 ──
417
- if (this.isAborted(taskId)) {
418
- ctx.aborted = true;
419
- throw new ABORTED_ERROR('编排执行终止,跳过准出审核');
420
- }
421
-
422
- // ── 阶段 3:TestCheck 准出审核 ──
423
- ctx.status = LoopStatus.CHECKING;
424
- await this._persistLoopState(ctx);
425
- this._emit(channel, SSE_EVENT.LOOP_CHECKING, {
426
- taskId, loop: loopNum
427
- });
428
-
429
- const checkResult = await this._runTestCheck(ctx, orchestratorResult);
430
- round.checkResult = checkResult;
431
- round.passed = checkResult.passed;
432
-
433
- // ── 判断是否准出通过 ──
434
- if (checkResult.passed) {
435
- this._log('info', `✅ 第 ${loopNum + 1} 轮准出审核通过,大循环结束`);
436
- ctx.status = LoopStatus.PASSED;
437
- round.endTime = nowCST();
438
- break;
439
- }
440
-
441
- // ── 准出未通过,准备下一轮 ──
442
- this._log('warn', `⚠ 第 ${loopNum + 1} 轮准出审核未通过,将进入下一轮`);
443
-
444
- } catch (err) {
445
- round.error = err.message;
446
- // 终止请求识别:ABORTED_ERROR(引擎检查点)或 SkillAbortedError
447
- // (Brain/TestCheck 的 SKILL 轮询检测到 abortSignal 抛出)。
448
- // 两者都必须跳出循环,不能当作普通轮次异常进入下一轮重规划。
449
- if (err instanceof ABORTED_ERROR || err?.name === 'SkillAbortedError' || this.isAborted(taskId)) {
450
- // 终止请求:同步 ctx.aborted 标志,直接跳出循环
451
- ctx.aborted = true;
452
- this._log('warn', `第 ${loopNum + 1} 轮被终止: ${err.message}`);
453
- round.endTime = nowCST();
454
- break;
455
- }
456
- this._log('error', `第 ${loopNum + 1} 轮执行异常: ${err.message}`);
457
- // 单轮异常不中断整个大循环,继续下一轮(除非是 abort)
458
- if (ctx.aborted) {
459
- round.endTime = nowCST();
460
- break;
461
- }
462
- }
463
-
464
- round.endTime = nowCST();
465
- ctx.currentLoop++;
466
-
467
- this._emit(channel, SSE_EVENT.LOOP_ROUND_END, {
468
- taskId, loop: loopNum, passed: round.passed
469
- });
470
-
471
- // 持久化循环状态
472
- await this._persistLoopState(ctx);
473
- }
474
-
475
- // ─── 循环结束,判定最终状态 ───
476
- if (ctx.aborted) {
477
- ctx.status = LoopStatus.ABORTED;
478
- this._emit(channel, SSE_EVENT.LOOP_ABORTED, { taskId });
479
- } else {
480
- const lastRound = ctx.rounds[ctx.rounds.length - 1];
481
- if (lastRound && lastRound.passed) {
482
- ctx.status = LoopStatus.PASSED;
483
- } else {
484
- ctx.status = LoopStatus.FAILED;
485
- ctx.error = `达到最大循环次数 ${ctx.maxLoops},准出审核仍未通过`;
486
- }
487
- }
488
-
489
- } catch (err) {
490
- ctx.status = LoopStatus.ERROR;
491
- ctx.error = err.message;
492
- this._emit(channel, SSE_EVENT.LOOP_ERROR, { taskId, error: err.message });
493
- this._log('error', `大循环异常: ${err.message}`);
494
- }
495
-
496
- ctx.endTime = nowCST();
497
-
498
- // 持久化最终状态
499
- await this._persistLoopState(ctx);
500
-
501
- this._emit(channel, SSE_EVENT.LOOP_COMPLETE, {
502
- taskId,
503
- status: ctx.status,
504
- totalRounds: ctx.rounds.length,
505
- passed: ctx.status === LoopStatus.PASSED
506
- });
507
-
508
- this._log('info', `═══ 大循环结束: status=${ctx.status}, rounds=${ctx.rounds.length} ═══`);
509
-
510
- return this._buildLoopResult(ctx);
511
- }
512
-
513
- /**
514
- * 终止大循环。
515
- *
516
- * 终止语义(同步返回,异步生效):
517
- * 1. 置位终止标志(isAborted 立即为 true,供各阶段轮询检查点退出)
518
- * 2. 终止 Orchestrator(通知运行中的 FlexRunner abortSession)
519
- * 3. 远程中止 Brain / TestCheck 正在运行的 opencode session(尽力而为)
520
- *
521
- * 各阶段的 SKILL 轮询(FlexRunner._pollSession / SkillExecutor.pollSession /
522
- * TestCheck._pollSession)在下一个轮询周期检测到标志后抛出终止并退出。
523
- *
524
- * @param {string} taskId
525
- * @returns {Promise<boolean>} 是否找到了运行中的循环(true = 终止请求已受理)
526
- */
527
- async abortLoop(taskId) {
528
- const ctx = this._loops.get(taskId);
529
- if (!ctx) {
530
- return false;
531
- }
532
-
533
- // 1. 置位终止标志(各阶段轮询检查点据此退出)
534
- ctx.aborted = true;
535
- const abortState = this._getAbortState(taskId);
536
- abortState.aborted = true;
537
-
538
- // 1b. 进入"中止中"过渡态:前端据此显示「任务中止中」,终止按钮置灰。
539
- // 各阶段轮询周期(秒级)检测到标志后退出,startLoop 收尾时置为终态 ABORTED。
540
- // 仅在仍处于活跃阶段时切换(已 PASSED/FAILED 的循环不回退状态)。
541
- const ACTIVE_STATES = [LoopStatus.PLANNING, LoopStatus.EXECUTING, LoopStatus.CHECKING];
542
- if (ACTIVE_STATES.includes(ctx.status)) {
543
- ctx.status = LoopStatus.ABORTING;
544
- this._emit(taskId, SSE_EVENT.LOOP_ABORTING, { taskId });
545
- // 立即落盘,防止重启后过渡态丢失(恢复流程会把 aborting 视为活跃态)
546
- await this._persistLoopState(ctx).catch(() => {});
547
- }
548
-
549
- // 2. 终止 Orchestrator(运行中的步骤由 FlexRunner abortSession 远程中止)
550
- try {
551
- const orch = this.orchestratorController._getOrchestrator(taskId);
552
- if (orch) {
553
- await orch.abort();
554
- }
555
- } catch {
556
- // Orchestrator 可能不存在或已结束
557
- }
558
-
559
- // 3. 远程中止 Brain / TestCheck 阶段正在运行的 opencode session
560
- // (否则 session 会在 opencode serve 后台继续跑到自然完成)
561
- const pendingSession = this._consumePendingSession(taskId);
562
- if (pendingSession) {
563
- await this._abortSessions([pendingSession]);
564
- }
565
-
566
- this._log('warn', `⏹ 大循环终止已请求: taskId=${taskId}`);
567
- return true;
568
- }
569
-
570
- /**
571
- * 获取循环进度。
572
- * @param {string} taskId
573
- * @returns {object|null}
574
- */
575
- getProgress(taskId) {
576
- const ctx = this._loops.get(taskId);
577
- if (!ctx) return null;
578
-
579
- return {
580
- taskId: ctx.taskId,
581
- status: ctx.status,
582
- currentLoop: ctx.currentLoop,
583
- maxLoops: ctx.maxLoops,
584
- startTime: ctx.startTime,
585
- endTime: ctx.endTime,
586
- error: ctx.error,
587
- rounds: ctx.rounds.map(r => {
588
- const plan = r.planningResult?.plan;
589
- const analysis = plan?.analysis || {};
590
- const taskDef = r.planningResult?.taskDef || plan?.taskDef || {};
591
- return {
592
- loop: r.loop,
593
- passed: r.passed,
594
- startTime: r.startTime,
595
- endTime: r.endTime,
596
- error: r.error,
597
- // ── Brain 拆解摘要 ──
598
- planningPlanId: plan?.planId || null,
599
- planningIsReplan: r.planningResult?.isReplan ?? false,
600
- planningStepCount: taskDef.steps?.length ?? 0,
601
- planningIntent: analysis.intent || null,
602
- planningDomain: analysis.domain || null,
603
- planningComplexity: analysis.complexity || null,
604
- planningFailureAnalysis: analysis.failureAnalysis || null,
605
- planningAdjustmentStrategy: analysis.adjustmentStrategy || null,
606
- // ── Orchestrator 编排摘要 ──
607
- orchestratorStatus: r.orchestratorResult?.status || null,
608
- // ── TestCheck 审核摘要 ──
609
- checkSummary: r.checkResult ? r.checkResult.summary : null,
610
- checkPassed: r.checkResult ? r.checkResult.passed : null,
611
- checkStatus: r.checkResult ? r.checkResult.status : null,
612
- checkId: r.checkResult ? r.checkResult.checkId : null,
613
- checkSessionId: r.checkResult ? r.checkResult.sessionId || null : null
614
- };
615
- })
616
- };
617
- }
618
-
619
- // ─── 内部:各阶段执行 ──────────────────────────────────────────────
620
-
621
- /**
622
- * 执行 Brain 阶段 — 首轮拆解或后续轮重规划。
623
- *
624
- * 首轮(loop=0):调用 Brain.planTask() 进行初始拆解
625
- * 后续轮(loop>0):调用 Brain.replanTask() 基于上轮审核结果重新生成目标
626
- *
627
- * @param {LoopContext} ctx
628
- * @param {object} originalParams - 原始启动参数
629
- * @returns {Promise<{plan: object, taskDef: object, isReplan: boolean}>}
630
- * @private
631
- */
632
- async _runBrain(ctx, originalParams) {
633
- const brain = this.brainController._getBrain();
634
-
635
- // 入口终止检查:拆解开始前已请求终止 → 不启动 Brain 的 opencode session
636
- if (this.isAborted(ctx.taskId)) {
637
- throw new ABORTED_ERROR('Brain 拆解前已请求终止');
638
- }
639
-
640
- if (ctx.currentLoop === 0) {
641
- // ── 首轮:初始拆解 ──
642
- this._log('info', `Brain 初始拆解: taskId=${ctx.taskId}`);
643
-
644
- const plan = await brain.planTask(ctx.taskDescription, {
645
- taskId: ctx.taskId,
646
- taskDir: ctx.taskDir,
647
- skillDirectory: ctx.skillDirectory,
648
- acceptanceCriteria: ctx.userAcceptanceCriteria,
649
- // 终止信号回调:Brain 内部 SKILL 轮询定期检查,置 true 时抛终止退出
650
- isAborted: () => this.isAborted(ctx.taskId),
651
- // Session 创建回调:登记到 AbortState 供 abortLoop 远程中止
652
- onSessionCreated: (sessionId) => this._trackSession(ctx.taskId, sessionId)
653
- });
654
-
655
- // 拆解期间收到终止请求(Session 已被远程中止,Brain 自然失败/退出)
656
- if (this.isAborted(ctx.taskId)) {
657
- throw new ABORTED_ERROR('Brain 拆解被终止');
658
- }
659
-
660
- this._trackSession(ctx.taskId, null);
661
-
662
- return { plan, taskDef: plan.taskDef, isReplan: false };
663
-
664
- } else {
665
- // ── 后续轮:重规划 ──
666
- const lastRound = ctx.rounds[ctx.rounds.length - 1];
667
- const lastCheckResult = lastRound ? lastRound.checkResult : null;
668
- const lastOrchResult = lastRound ? lastRound.orchestratorResult : null;
669
-
670
- this._log('info', `Brain 重规划: taskId=${ctx.taskId}, loop=${ctx.currentLoop}`);
671
-
672
- const plan = await brain.replanTask(ctx.taskDescription, {
673
- taskId: ctx.taskId,
674
- taskDir: ctx.taskDir,
675
- skillDirectory: ctx.skillDirectory,
676
- acceptanceCriteria: ctx.userAcceptanceCriteria,
677
- previousCheckResult: lastCheckResult,
678
- previousOrchestratorResult: lastOrchResult,
679
- loopCount: ctx.currentLoop,
680
- // 终止信号回调:重规划轮同样接入终止检查
681
- isAborted: () => this.isAborted(ctx.taskId),
682
- onSessionCreated: (sessionId) => this._trackSession(ctx.taskId, sessionId)
683
- });
684
-
685
- // 拆解期间收到终止请求(Session 已被远程中止,Brain 自然失败/退出)
686
- if (this.isAborted(ctx.taskId)) {
687
- throw new ABORTED_ERROR('Brain 重规划被终止');
688
- }
689
-
690
- this._trackSession(ctx.taskId, null);
691
-
692
- return { plan, taskDef: plan.taskDef, isReplan: true };
693
- }
694
- }
695
-
696
- /**
697
- * 执行 Orchestrator 阶段 — 加载任务并等待执行完成。
698
- *
699
- * 通过 BrainController 的 _invokeController 模式调用 OrchestratorController:
700
- * 1. load — 加载任务定义
701
- * 2. start — 启动编排
702
- * 3. 等待编排完成(轮询状态)
703
- *
704
- * @param {LoopContext} ctx
705
- * @param {object} planningResult - Brain 阶段返回的 { plan, taskDef }
706
- * @returns {Promise<object>} Orchestrator 执行结果摘要
707
- * @private
708
- */
709
- async _runOrchestrator(ctx, planningResult) {
710
- const { taskDef } = planningResult;
711
- const orchController = this.orchestratorController;
712
-
713
- // 入口终止检查:Brain 完成后终止请求已到位 → 不加载编排(否则会完整跑一轮新编排)
714
- if (this.isAborted(ctx.taskId)) {
715
- throw new ABORTED_ERROR('编排执行前已请求终止');
716
- }
717
-
718
- // ── 加载任务 ──
719
- const loadReq = { body: taskDef };
720
- const loadRes = this._createMockResponse();
721
- await orchController.load(loadReq, loadRes);
722
-
723
- if (loadRes._status >= 400) {
724
- throw new Error(`Orchestrator 加载失败: ${loadRes._data?.error || loadRes._data?.message}`);
725
- }
726
-
727
- // 加载后终止检查:终止请求在 load 过程中到达 → 不启动编排
728
- if (this.isAborted(ctx.taskId)) {
729
- throw new ABORTED_ERROR('编排启动前已请求终止');
730
- }
731
-
732
- // ── 启动编排 ──
733
- const startReq = { body: { taskId: ctx.taskId } };
734
- const startRes = this._createMockResponse();
735
- await orchController.start(startReq, startRes);
736
-
737
- if (startRes._status >= 400) {
738
- throw new Error(`Orchestrator 启动失败: ${startRes._data?.error || startRes._data?.message}`);
739
- }
740
-
741
- // ── 等待编排完成 ──
742
- const orch = orchController._getOrchestrator(ctx.taskId);
743
- if (!orch) {
744
- throw new Error(`Orchestrator 实例未找到: taskId=${ctx.taskId}`);
745
- }
746
-
747
- // Orchestrator.start() 是异步执行的(Controller 中 fire-and-forget)
748
- // 需要轮询等待编排状态变为终态
749
- const result = await this._waitForOrchestrator(ctx.taskId, orch);
750
-
751
- return result;
752
- }
753
-
754
- /**
755
- * 等待 Orchestrator 执行完成。
756
- *
757
- * 轮询编排状态,直到进入终态(COMPLETED / FAILED / ABORTED / DEADLOCKED)。
758
- *
759
- * @param {string} taskId
760
- * @param {object} orch - Orchestrator 实例
761
- * @returns {Promise<object>} 编排结果摘要
762
- * @private
763
- */
764
- async _waitForOrchestrator(taskId, orch) {
765
- const POLL_MS = 5000;
766
- const TIMEOUT_MS = 120 * 60 * 1000; // 120 分钟超时
767
- const deadline = Date.now() + TIMEOUT_MS;
768
-
769
- while (Date.now() < deadline) {
770
- const ctx = this._loops.get(taskId);
771
- if (ctx && ctx.aborted) {
772
- break;
773
- }
774
-
775
- await this._sleep(POLL_MS);
776
-
777
- const status = orch.status;
778
- // 终态判断
779
- if (status === 'completed' || status === 'failed' ||
780
- status === 'aborted' || status === 'deadlocked') {
781
- break;
782
- }
783
- }
784
-
785
- const progress = orch.getProgress();
786
- this._log('info', `Orchestrator 执行结束: status=${progress.status}`);
787
-
788
- return {
789
- status: progress.status,
790
- taskId: progress.taskId,
791
- stats: progress.stats,
792
- steps: progress.steps,
793
- error: progress.error,
794
- startTime: progress.startTime,
795
- endTime: progress.endTime
796
- };
797
- }
798
-
799
- /**
800
- * 执行 TestCheck 阶段 — 准出审核。
801
- *
802
- * @param {LoopContext} ctx
803
- * @param {object} orchestratorResult - Orchestrator 执行结果
804
- * @returns {Promise<object>} CheckResult
805
- * @private
806
- */
807
- async _runTestCheck(ctx, orchestratorResult) {
808
- const testCheck = this._getTestCheck();
809
-
810
- // 入口终止检查:编排结束后终止请求已到位 → 不启动审核 session
811
- if (this.isAborted(ctx.taskId)) {
812
- throw new ABORTED_ERROR('准出审核前已请求终止');
813
- }
814
-
815
- // 从 Brain plan 中提取验收标准
816
- const lastPlan = ctx.rounds.length > 0
817
- ? ctx.rounds[ctx.rounds.length - 1].planningResult?.plan
818
- : null;
819
-
820
- // 优先使用当前轮的 plan,否则从 Brain 加载
821
- let acceptanceCriteria = [];
822
- if (lastPlan && lastPlan.analysis && lastPlan.analysis.acceptanceCriteria) {
823
- acceptanceCriteria = lastPlan.analysis.acceptanceCriteria;
824
- } else {
825
- // 从持久化加载 brain.json
826
- try {
827
- const brain = this.brainController._getBrain();
828
- const plan = await brain.loadPlan('', { taskId: ctx.taskId });
829
- if (plan && plan.analysis && plan.analysis.acceptanceCriteria) {
830
- acceptanceCriteria = plan.analysis.acceptanceCriteria;
831
- }
832
- } catch {
833
- // 加载失败使用空标准
834
- }
835
- }
836
-
837
- const result = await testCheck.runCheck({
838
- taskId: ctx.taskId,
839
- taskDir: ctx.taskDir,
840
- taskDescription: ctx.taskDescription,
841
- acceptanceCriteria,
842
- orchestratorResult,
843
- loopCount: ctx.currentLoop,
844
- skillDirectory: ctx.skillDirectory,
845
- // 终止信号回调:审核 SKILL 轮询定期检查,置 true 时抛终止退出
846
- isAborted: () => this.isAborted(ctx.taskId),
847
- // Session 创建回调:登记到 AbortState 供 abortLoop 远程中止
848
- onSessionCreated: (sessionId) => this._trackSession(ctx.taskId, sessionId)
849
- });
850
-
851
- // 审核期间收到终止请求(session 已被远程中止)
852
- if (this.isAborted(ctx.taskId)) {
853
- this._trackSession(ctx.taskId, null);
854
- throw new ABORTED_ERROR('准出审核被终止');
855
- }
856
-
857
- this._trackSession(ctx.taskId, null);
858
-
859
- return result;
860
- }
861
-
862
- // ─── 内部:持久化 ──────────────────────────────────────────────────
863
-
864
- /**
865
- * 持久化大循环状态到 db/task/{taskId}/loop.json。
866
- *
867
- * 文件布局:
868
- * db/task/{taskId}/
869
- * ├── brain.json ← Brain 拆解计划(最新快照)
870
- * ├── brain_loop0.json ← 各轮 Brain 计划(独立保留)
871
- * ├── brain_loop1.json
872
- * ├── orchestration.json ← Orchestrator 编排状态(最新快照)
873
- * ├── orchestration_loop0.json ← 各轮编排状态(独立保留)
874
- * ├── orchestration_loop1.json
875
- * ├── stepid_1_loop0.json ← 各轮步骤执行记录(独立保留)
876
- * ├── stepid_1_loop1.json
877
- * ├── stepId1_loop0_result_0.md ← 各轮步骤结果文件(独立保留)
878
- * ├── stepId1_loop1_result_0.md
879
- * ├── loop.json ← LoopEngine 大循环状态(本方法写入)
880
- * ├── testcheck_loop0.json ← 各轮审核结果
881
- * └── ...
882
- *
883
- * @param {LoopContext} ctx
884
- * @private
885
- */
886
- async _persistLoopState(ctx) {
887
- try {
888
- const json = this.storage.json('task');
889
- const state = {
890
- taskId: ctx.taskId,
891
- taskDescription: ctx.taskDescription,
892
- userAcceptanceCriteria: ctx.userAcceptanceCriteria,
893
- taskDir: ctx.taskDir,
894
- skillDirectory: ctx.skillDirectory,
895
- maxLoops: ctx.maxLoops,
896
- currentLoop: ctx.currentLoop,
897
- status: ctx.status,
898
- startTime: ctx.startTime,
899
- endTime: ctx.endTime,
900
- error: ctx.error,
901
- aborted: ctx.aborted,
902
- rounds: ctx.rounds.map(r => {
903
- const plan = r.planningResult?.plan;
904
- const analysis = plan?.analysis || {};
905
- const taskDef = r.planningResult?.taskDef || plan?.taskDef || {};
906
- return {
907
- loop: r.loop,
908
- startTime: r.startTime,
909
- endTime: r.endTime,
910
- passed: r.passed,
911
- error: r.error,
912
- // ── Brain 拆解摘要 ──
913
- planningPlanId: plan?.planId || null,
914
- planningIsReplan: r.planningResult?.isReplan ?? false,
915
- planningLoopCount: plan?.loopCount ?? r.loop,
916
- planningStepCount: taskDef.steps?.length ?? 0,
917
- planningIntent: analysis.intent || null,
918
- planningDomain: analysis.domain || null,
919
- planningComplexity: analysis.complexity || null,
920
- planningFailureAnalysis: analysis.failureAnalysis || null,
921
- planningAdjustmentStrategy: analysis.adjustmentStrategy || null,
922
- planningReplanInfo: taskDef.replanInfo || null,
923
- // ── Orchestrator 编排摘要 ──
924
- orchestratorStatus: r.orchestratorResult?.status || null,
925
- // ── TestCheck 审核摘要 ──
926
- checkPassed: r.checkResult?.passed ?? null,
927
- checkStatus: r.checkResult?.status || null,
928
- checkSummary: r.checkResult?.summary || null,
929
- checkId: r.checkResult?.checkId || null,
930
- checkSessionId: r.checkResult?.sessionId || null
931
- };
932
- }),
933
- persistedAt: nowCST()
934
- };
935
- await json.write(`${ctx.taskId}/loop`, state);
936
- } catch (err) {
937
- this._log('warn', `持久化大循环状态失败: ${err.message}`);
938
- }
939
- }
940
-
941
- /**
942
- * 从持久化文件恢复大循环状态。
943
- *
944
- * @param {string} taskId
945
- * @returns {Promise<LoopContext|null>}
946
- */
947
- async restoreLoop(taskId) {
948
- try {
949
- const json = this.storage.json('task');
950
- const state = await json.read(`${taskId}/loop`);
951
- if (!state) return null;
952
-
953
- /** @type {LoopContext} */
954
- const ctx = {
955
- taskId: state.taskId,
956
- taskDescription: state.taskDescription || '',
957
- userAcceptanceCriteria: state.userAcceptanceCriteria || '',
958
- taskDir: state.taskDir || '',
959
- skillDirectory: state.skillDirectory || '',
960
- maxLoops: state.maxLoops || DEFAULT_MAX_LOOPS,
961
- currentLoop: state.currentLoop || 0,
962
- // 恢复时 aborting(中止中的过渡态)归一化为 aborted:
963
- // 重启后循环执行体已不存在,无法也不应继续"等待中止完成"
964
- status: state.status === LoopStatus.ABORTING
965
- ? LoopStatus.ABORTED
966
- : (state.status || LoopStatus.IDLE),
967
- startTime: state.startTime || null,
968
- endTime: state.endTime || null,
969
- rounds: Array.isArray(state.rounds) ? state.rounds.map(r => ({
970
- loop: r.loop,
971
- startTime: r.startTime,
972
- endTime: r.endTime,
973
- // 从持久化的摘要字段恢复 planningResult(运行时完整对象不可恢复,
974
- // 但恢复关键元数据供 getProgress 展示)
975
- planningResult: r.planningPlanId ? {
976
- plan: {
977
- planId: r.planningPlanId,
978
- isReplan: r.planningIsReplan ?? false,
979
- loopCount: r.planningLoopCount ?? r.loop,
980
- analysis: {
981
- intent: r.planningIntent || '',
982
- domain: r.planningDomain || '',
983
- complexity: r.planningComplexity || '',
984
- failureAnalysis: r.planningFailureAnalysis || '',
985
- adjustmentStrategy: r.planningAdjustmentStrategy || ''
986
- },
987
- taskDef: { replanInfo: r.planningReplanInfo || null }
988
- },
989
- taskDef: null,
990
- isReplan: r.planningIsReplan ?? false
991
- } : null,
992
- orchestratorResult: r.orchestratorStatus ? { status: r.orchestratorStatus } : null,
993
- checkResult: r.checkId ? {
994
- checkId: r.checkId,
995
- passed: r.checkPassed ?? false,
996
- status: r.checkStatus || CheckStatus.IDLE,
997
- summary: r.checkSummary || '',
998
- sessionId: r.checkSessionId || ''
999
- } : null,
1000
- passed: r.passed || false,
1001
- error: r.error || null
1002
- })) : [],
1003
- error: state.error || null,
1004
- aborted: false
1005
- };
1006
-
1007
- this._loops.set(taskId, ctx);
1008
-
1009
- // ── 补充加载:旧版 loop.json 缺少 checkSessionId,
1010
- // 从 testcheck_loopN.json 中补全各轮 checkResult.sessionId ──
1011
- for (const round of ctx.rounds) {
1012
- if (round.checkResult && !round.checkResult.sessionId) {
1013
- try {
1014
- const checkData = await json.read(`${taskId}/testcheck_loop${round.loop}`);
1015
- if (checkData && checkData.sessionId) {
1016
- round.checkResult.sessionId = checkData.sessionId;
1017
- }
1018
- } catch {
1019
- // testcheck_loopN.json 可能不存在(审核未执行),跳过
1020
- }
1021
- }
1022
- }
1023
- return ctx;
1024
- } catch {
1025
- return null;
1026
- }
1027
- }
1028
-
1029
- // ─── 内部工具 ──────────────────────────────────────────────────────
1030
-
1031
- /**
1032
- * 构建最终的 LoopResult 返回值。
1033
- * @param {LoopContext} ctx
1034
- * @returns {LoopResult}
1035
- * @private
1036
- */
1037
- _buildLoopResult(ctx) {
1038
- const lastPassedRound = ctx.rounds.find(r => r.passed);
1039
-
1040
- return {
1041
- taskId: ctx.taskId,
1042
- status: ctx.status,
1043
- passed: ctx.status === LoopStatus.PASSED,
1044
- totalRounds: ctx.rounds.length,
1045
- currentLoop: ctx.currentLoop,
1046
- maxLoops: ctx.maxLoops,
1047
- startTime: ctx.startTime,
1048
- endTime: ctx.endTime,
1049
- error: ctx.error,
1050
- rounds: ctx.rounds.map(r => ({
1051
- loop: r.loop,
1052
- passed: r.passed,
1053
- startTime: r.startTime,
1054
- endTime: r.endTime,
1055
- error: r.error,
1056
- planId: r.planningResult?.plan?.planId || null,
1057
- orchestratorStatus: r.orchestratorResult?.status || null,
1058
- checkPassed: r.checkResult?.passed ?? null,
1059
- checkSummary: r.checkResult?.summary || null,
1060
- improvementSuggestions: r.checkResult?.improvementSuggestions || []
1061
- })),
1062
- finalCheckResult: lastPassedRound
1063
- ? lastPassedRound.checkResult
1064
- : (ctx.rounds[ctx.rounds.length - 1]?.checkResult || null)
1065
- };
1066
- }
1067
-
1068
- /**
1069
- * 创建模拟的 ResponseContext(用于调用 Controller 方法)。
1070
- * @returns {object}
1071
- * @private
1072
- */
1073
- _createMockResponse() {
1074
- return {
1075
- _data: null,
1076
- _status: 200,
1077
- json(data, status = 200) {
1078
- this._data = data;
1079
- this._status = status;
1080
- },
1081
- error(status, message) {
1082
- this._data = { error: message };
1083
- this._status = status;
1084
- }
1085
- };
1086
- }
1087
-
1088
- /**
1089
- * Promise 版 sleep。
1090
- * @param {number} ms
1091
- * @returns {Promise<void>}
1092
- * @private
1093
- */
1094
- _sleep(ms) {
1095
- return new Promise((resolve) => setTimeout(resolve, ms));
1096
- }
1097
-
1098
- /**
1099
- * 统一日志输出。
1100
- * @param {'info'|'warn'|'error'} level
1101
- * @param {string} message
1102
- * @private
1103
- */
1104
- _log(level, message) {
1105
- const prefix = '[LoopEngine]';
1106
- const ts = nowCST();
1107
- switch (level) {
1108
- case 'error':
1109
- console.error(`${ts} ${prefix} ❌ ${message}`);
1110
- break;
1111
- case 'warn':
1112
- console.warn(`${ts} ${prefix} ⚠ ${message}`);
1113
- break;
1114
- default:
1115
- console.info(`${ts} ${prefix} ℹ ${message}`);
1116
- }
1117
- }
1118
- }
1119
-
1120
- // ─── 辅助函数 ──────────────────────────────────────────────────────────────
1121
-
1122
- /**
1123
- * 生成 taskId。
1124
- * @returns {string}
1125
- */
1126
- function _generateTaskId() {
1127
- const ts = Date.now().toString(36);
1128
- const rand = Math.random().toString(36).substring(2, 6);
1129
- return `task_${ts}${rand}`;
1130
- }
1131
-
1132
- export { LoopEngine, ABORTED_ERROR };
1133
- export default LoopEngine;
1
+ /**
2
+ * LoopEngine —— 大循环协调引擎(Loop Engineering)
3
+ *
4
+ * 职责:
5
+ * - 协调 Brain → Orchestrator → FlexRunner → TestCheck 的完整闭环
6
+ * - Orchestrator 执行完成后,调用 TestCheck 进行准出审核
7
+ * - 审核未通过时,调用 Brain.replanTask() 重新生成目标,进入下一轮循环
8
+ * - 循环终止条件:准出通过 OR 达到最大循环次数
9
+ * - 全流程状态持久化,支持中断恢复
10
+ * - 通过 SSE 实时推送大循环进度
11
+ *
12
+ * 架构角色(Loop Engineering 大循环):
13
+ *
14
+ * ┌──────────────────────────────────────────────────────────────┐
15
+ * │ LoopEngine │
16
+ * │ │
17
+ * │ ┌─────────┐ ┌──────────────┐ ┌──────────┐ │
18
+ * │ │ Brain │───→│ Orchestrator │───→│ TestCheck│ │
19
+ * │ │ 拆解/重规│ │ 编排执行 │ │ 准出审核 │ │
20
+ * │ └────▲────┘ └──────────────┘ └────┬─────┘ │
21
+ * │ │ │ │
22
+ * │ └────────── 未通过 ────────────────┘ │
23
+ * │ │
24
+ * │ 循环终止:准出通过 OR maxLoops │
25
+ * └──────────────────────────────────────────────────────────────┘
26
+ *
27
+ * 设计原则:
28
+ * - 高内聚:大循环协调逻辑集中于此,不感知各子模块的内部实现
29
+ * - 低耦合:通过依赖注入 Brain / OrchestratorController / TestCheck
30
+ * - 单一职责:LoopEngine 只管循环驱动和状态流转,不关心拆解/编排/审核细节
31
+ * - 可恢复:循环状态持久化,服务重启后可恢复循环上下文
32
+ */
33
+
34
+ import path from 'node:path';
35
+ import { fileURLToPath } from 'node:url';
36
+
37
+ import { Brain, BrainStatus } from '../Brain/index.js';
38
+ import { TestCheck, CheckStatus } from '../TestCheck/index.js';
39
+ import { nowCST } from '../library/cstTime.js';
40
+ import { createStorage } from '../DbUse/index.js';
41
+
42
+ // ─── 常量 ────────────────────────────────────────────────────────────────────
43
+
44
+ /** LoopEngine 循环状态 */
45
+ export const LoopStatus = Object.freeze({
46
+ IDLE: 'idle', // 未启动
47
+ PLANNING: 'planning', // Brain 拆解/重规划中
48
+ EXECUTING: 'executing', // Orchestrator 执行中
49
+ CHECKING: 'checking', // TestCheck 准出审核中
50
+ ABORTING: 'aborting', // 终止已请求,等待各阶段轮询退出(过渡态)
51
+ PASSED: 'passed', // 准出通过,循环结束
52
+ FAILED: 'failed', // 达到最大循环次数仍未通过
53
+ ABORTED: 'aborted', // 被手动终止
54
+ ERROR: 'error' // 循环过程出错
55
+ });
56
+
57
+ /** SSE 事件类型 */
58
+ const SSE_EVENT = Object.freeze({
59
+ LOOP_START: 'loop:start',
60
+ LOOP_ROUND_START: 'loop:round:start',
61
+ LOOP_PLANNING: 'loop:planning',
62
+ LOOP_EXECUTING: 'loop:executing',
63
+ LOOP_CHECKING: 'loop:checking',
64
+ LOOP_ROUND_END: 'loop:round:end',
65
+ LOOP_COMPLETE: 'loop:complete',
66
+ LOOP_ERROR: 'loop:error',
67
+ LOOP_ABORTING: 'loop:aborting',
68
+ LOOP_ABORTED: 'loop:aborted'
69
+ });
70
+
71
+ /** 默认最大循环次数 */
72
+ const DEFAULT_MAX_LOOPS = 3;
73
+
74
+ /**
75
+ * ABORTED_ERROR —— 大循环终止专用错误。
76
+ *
77
+ * 由各阶段(Brain 拆解 / Orchestrator 等待 / TestCheck 审核)检测到终止请求后抛出,
78
+ * startLoop 的 catch 分支识别本错误后立即跳出循环(不再进入后续阶段),
79
+ * 区别于普通的轮次执行异常(后者会继续下一轮)。
80
+ */
81
+ class ABORTED_ERROR extends Error {
82
+ /**
83
+ * @param {string} [message]
84
+ */
85
+ constructor(message = '大循环已终止') {
86
+ super(message);
87
+ this.name = 'AbortError';
88
+ }
89
+ }
90
+
91
+ // ─── LoopEngine 类 ───────────────────────────────────────────────────────────
92
+
93
+ /**
94
+ * LoopEngine —— 大循环协调引擎
95
+ */
96
+ class LoopEngine {
97
+ /**
98
+ * @param {object} deps - 依赖注入
99
+ * @param {object} deps.brainController - BrainController 实例(必须)
100
+ * @param {object} deps.orchestratorController - OrchestratorController 实例(必须)
101
+ * @param {TestCheck} [deps.testCheck] - TestCheck 实例(可选,自动创建)
102
+ * @param {object} [deps.storage] - 存储门面
103
+ * @param {object} [deps.sseHub] - SseHub 实例
104
+ * @param {string} [deps.skillDirectory] - SKILL 目录路径
105
+ */
106
+ constructor(deps = {}) {
107
+ if (!deps.brainController) {
108
+ throw new Error('LoopEngine: brainController 为必填依赖');
109
+ }
110
+ if (!deps.orchestratorController) {
111
+ throw new Error('LoopEngine: orchestratorController 为必填依赖');
112
+ }
113
+
114
+ this.brainController = deps.brainController;
115
+ this.orchestratorController = deps.orchestratorController;
116
+ this.storage = deps.storage || createStorage();
117
+ this.sseHub = deps.sseHub || null;
118
+ this.skillDirectory = deps.skillDirectory || null;
119
+
120
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
121
+ this.skillDirectory = deps.skillDirectory ||
122
+ path.resolve(__dirname, '..', 'Skills');
123
+
124
+ // TestCheck 实例(懒初始化)
125
+ this._testCheck = deps.testCheck || null;
126
+
127
+ /**
128
+ * AbortState —— 大循环终止状态(由 abortLoop 驱动,各阶段轮询检查)。
129
+ *
130
+ * 场景:setAbortFlag 置位后,正在运行的 Brain/TestCheck SKILL 轮询
131
+ * 需要在下一个轮询周期感知并退出;同时记录活跃 sessionId 供 abortLoop
132
+ * 远程中止 opencode session(否则 session 会在后台继续跑到自然完成)。
133
+ */
134
+ this._abortStates = new Map();
135
+
136
+ /** @type {Map<string, LoopContext>} key = taskId */
137
+ this._loops = new Map();
138
+ }
139
+
140
+ // ─── 属性访问 ──────────────────────────────────────────────────────
141
+
142
+ /**
143
+ * 获取 TestCheck 实例(懒初始化)。
144
+ * @returns {TestCheck}
145
+ * @private
146
+ */
147
+ _getTestCheck() {
148
+ if (!this._testCheck) {
149
+ this._testCheck = new TestCheck({
150
+ storage: this.storage,
151
+ sseHub: this.sseHub,
152
+ skillDirectory: this.skillDirectory
153
+ });
154
+ }
155
+ return this._testCheck;
156
+ }
157
+
158
+ /**
159
+ * 获取循环上下文。
160
+ * @param {string} taskId
161
+ * @returns {LoopContext|undefined}
162
+ */
163
+ getLoop(taskId) {
164
+ return this._loops.get(taskId);
165
+ }
166
+
167
+ /**
168
+ * 获取所有循环。
169
+ * @returns {LoopContext[]}
170
+ */
171
+ getLoops() {
172
+ return Array.from(this._loops.values());
173
+ }
174
+
175
+ // ─── 终止状态管理 ──────────────────────────────────────────────────
176
+
177
+ /**
178
+ * 获取(或惰性创建)任务的 AbortState。
179
+ * @param {string} taskId
180
+ * @returns {{ aborted: boolean, sessionId: string|null }}
181
+ * @private
182
+ */
183
+ _getAbortState(taskId) {
184
+ let st = this._abortStates.get(taskId);
185
+ if (!st) {
186
+ st = { aborted: false, sessionId: null };
187
+ this._abortStates.set(taskId, st);
188
+ }
189
+ return st;
190
+ }
191
+
192
+ /**
193
+ * 判断任务是否已被请求终止。
194
+ * @param {string} taskId
195
+ * @returns {boolean}
196
+ */
197
+ isAborted(taskId) {
198
+ const st = this._abortStates.get(taskId);
199
+ return Boolean(st && st.aborted);
200
+ }
201
+
202
+ /**
203
+ * 登记当前阶段正在运行的 opencode sessionId(Brain 拆解 / TestCheck 审核)。
204
+ *
205
+ * 供 abortLoop 远程中止 session;sessionId 更新时若已请求终止则立即中止新 session。
206
+ *
207
+ * @param {string} taskId
208
+ * @param {string|null} sessionId - null 表示清除
209
+ * @private
210
+ */
211
+ _trackSession(taskId, sessionId) {
212
+ const st = this._getAbortState(taskId);
213
+
214
+ // 终止已请求:新登记的 session 直接远程中止,防止 Brain/审核
215
+ // 轮询间隙里新建的 session 继续在后台运行
216
+ if (st.aborted && sessionId) {
217
+ this._abortSessions([sessionId]);
218
+ }
219
+
220
+ if (sessionId) {
221
+ st.pendingSessionId = sessionId; // 尚未被消费的 session
222
+ delete st.abortedSessionIds?.[sessionId];
223
+ } else {
224
+ st.pendingSessionId = null;
225
+ }
226
+ }
227
+
228
+ /**
229
+ * 消费未中止的 pending session(轮询线程检测到终止时调用,返回应立即远程中止的 sessionId)。
230
+ * @param {string} taskId
231
+ * @returns {string|null}
232
+ * @private
233
+ */
234
+ _consumePendingSession(taskId) {
235
+ const st = this._abortStates.get(taskId);
236
+ if (!st || !st.pendingSessionId) return null;
237
+ const sid = st.pendingSessionId;
238
+ st.pendingSessionId = null;
239
+ return sid;
240
+ }
241
+
242
+ /**
243
+ * 远程中止 opencode sessions(尽力而为,失败仅记日志)。
244
+ * @param {string[]} sessionIds
245
+ * @returns {Promise<void>}
246
+ * @private
247
+ */
248
+ async _abortSessions(sessionIds) {
249
+ if (!sessionIds || sessionIds.length === 0) return;
250
+
251
+ let cli = null;
252
+ try {
253
+ const mod = await import('../CLI/cli.js');
254
+ cli = new mod.CLIController();
255
+ } catch (err) {
256
+ this._log('warn', `_abortSessions: 加载 CLIController 失败: ${err.message}`);
257
+ return;
258
+ }
259
+
260
+ for (const sid of sessionIds) {
261
+ if (!sid) continue;
262
+ try {
263
+ await cli.abortSession(sid);
264
+ this._log('info', `_abortSessions: 已远程中止 session ${sid}`);
265
+ } catch (err) {
266
+ // session 可能已自然结束,中止失败不阻断终止流程
267
+ this._log('warn', `_abortSessions: 中止 session ${sid} 失败: ${err.message}`);
268
+ }
269
+ }
270
+ }
271
+
272
+ // ─── SSE 推送 ──────────────────────────────────────────────────────
273
+
274
+ /**
275
+ * 推送循环进度事件。
276
+ * @param {string} channel
277
+ * @param {string} eventType
278
+ * @param {object} data
279
+ * @private
280
+ */
281
+ _emit(channel, eventType, data) {
282
+ if (this.sseHub) {
283
+ this.sseHub.emit(channel, eventType, data);
284
+ }
285
+ }
286
+
287
+ // ─── 核心方法 ──────────────────────────────────────────────────────
288
+
289
+ /**
290
+ * 启动大循环 — 从自然语言任务描述开始,执行完整的拆解→编排→审核闭环。
291
+ *
292
+ * 流程:
293
+ * 1. Brain.planTask() 拆解任务
294
+ * 2. Orchestrator 加载并执行
295
+ * 3. TestCheck.runCheck() 准出审核
296
+ * 4. 未通过 → Brain.replanTask() 重新生成目标 → 回到步骤 2
297
+ * 5. 循环终止:准出通过 OR 达到最大循环次数
298
+ *
299
+ * @param {object} params
300
+ * @param {string} params.taskDescription - 自然语言任务描述(必填)
301
+ * @param {string} [params.taskId] - 指定 taskId
302
+ * @param {string} [params.taskDir] - 指定工作目录
303
+ * @param {string} [params.skillDirectory] - 覆盖 SKILL 目录
304
+ * @param {string} [params.acceptanceCriteria] - 用户提供的验收标准
305
+ * @param {number} [params.maxLoops] - 最大循环次数(默认 3)
306
+ * @returns {Promise<LoopResult>}
307
+ */
308
+ async startLoop(params = {}) {
309
+ const { taskDescription } = params;
310
+
311
+ if (!taskDescription || typeof taskDescription !== 'string') {
312
+ throw new Error('LoopEngine.startLoop: taskDescription 不能为空');
313
+ }
314
+
315
+ const maxLoops = typeof params.maxLoops === 'number' && params.maxLoops > 0
316
+ ? params.maxLoops
317
+ : DEFAULT_MAX_LOOPS;
318
+
319
+ // 生成 taskId(如果未指定)
320
+ const taskId = params.taskId || _generateTaskId();
321
+ const skillDir = params.skillDirectory || this.skillDirectory;
322
+
323
+ // 解析 taskDir:与 JSON 状态文件(storage.json('task'))保持同一目录 db/task/{taskId}
324
+ const taskDir = params.taskDir ||
325
+ path.resolve(this.storage.manager.rootDir, 'task', taskId);
326
+
327
+ const channel = taskId;
328
+ const startTime = nowCST();
329
+
330
+ /** @type {LoopContext} */
331
+ const ctx = {
332
+ taskId,
333
+ taskDescription,
334
+ userAcceptanceCriteria: params.acceptanceCriteria || '',
335
+ taskDir,
336
+ skillDirectory: skillDir,
337
+ maxLoops,
338
+ currentLoop: 0,
339
+ status: LoopStatus.IDLE,
340
+ startTime,
341
+ endTime: null,
342
+ rounds: [],
343
+ error: null,
344
+ aborted: false
345
+ };
346
+
347
+ this._loops.set(taskId, ctx);
348
+
349
+ // 重置终止状态(同 taskId 重新启动场景)
350
+ const abortState = this._getAbortState(taskId);
351
+ abortState.aborted = false;
352
+ abortState.pendingSessionId = null;
353
+
354
+ // 立即持久化 loop.json,确保大循环启动后任务即可在列表中可见
355
+ // 防止系统重启时因 loop.json 未写入导致任务丢失
356
+ await this._persistLoopState(ctx);
357
+
358
+ this._emit(channel, SSE_EVENT.LOOP_START, {
359
+ taskId, maxLoops, taskDescription: taskDescription.slice(0, 200), startTime
360
+ });
361
+
362
+ try {
363
+ // ─── 大循环主体 ───
364
+ while (ctx.currentLoop < ctx.maxLoops && !ctx.aborted) {
365
+ const loopNum = ctx.currentLoop;
366
+
367
+ this._log('info', `═══ 大循环第 ${loopNum + 1}/${ctx.maxLoops} 轮开始 ═══`);
368
+ this._emit(channel, SSE_EVENT.LOOP_ROUND_START, {
369
+ taskId, loop: loopNum, total: ctx.maxLoops
370
+ });
371
+
372
+ /** @type {RoundResult} */
373
+ const round = {
374
+ loop: loopNum,
375
+ startTime: nowCST(),
376
+ endTime: null,
377
+ planningResult: null,
378
+ orchestratorResult: null,
379
+ checkResult: null,
380
+ passed: false,
381
+ error: null
382
+ };
383
+
384
+ // 立即将 round 加入 ctx.rounds,使前端轮询能实时看到各阶段进度
385
+ ctx.rounds.push(round);
386
+
387
+ try {
388
+ // ── 阶段 1:Brain 拆解 / 重规划 ──
389
+ ctx.status = LoopStatus.PLANNING;
390
+ await this._persistLoopState(ctx);
391
+ this._emit(channel, SSE_EVENT.LOOP_PLANNING, {
392
+ taskId, loop: loopNum, isReplan: loopNum > 0
393
+ });
394
+
395
+ const planningResult = await this._runBrain(ctx, params);
396
+ round.planningResult = planningResult;
397
+
398
+ // ── 终止检查点:Brain 拆解期间收到终止请求 → 不进入编排阶段 ──
399
+ // (Brain 内部的 SKILL 轮询会因 abort 提前退出并抛 ABORTED 错误,
400
+ // 此检查兜底处理 Brain 自然完成但终止已请求的场景)
401
+ if (this.isAborted(taskId)) {
402
+ ctx.aborted = true;
403
+ throw new ABORTED_ERROR('Brain 拆解完成前收到终止请求');
404
+ }
405
+
406
+ // ── 阶段 2:Orchestrator 编排执行 ──
407
+ ctx.status = LoopStatus.EXECUTING;
408
+ await this._persistLoopState(ctx);
409
+ this._emit(channel, SSE_EVENT.LOOP_EXECUTING, {
410
+ taskId, loop: loopNum
411
+ });
412
+
413
+ const orchestratorResult = await this._runOrchestrator(ctx, planningResult);
414
+ round.orchestratorResult = orchestratorResult;
415
+
416
+ // ── 终止检查点:编排期间收到终止请求(waitFor 检测到后提前返回)→ 不进入审核阶段 ──
417
+ if (this.isAborted(taskId)) {
418
+ ctx.aborted = true;
419
+ throw new ABORTED_ERROR('编排执行终止,跳过准出审核');
420
+ }
421
+
422
+ // ── 阶段 3:TestCheck 准出审核 ──
423
+ ctx.status = LoopStatus.CHECKING;
424
+ await this._persistLoopState(ctx);
425
+ this._emit(channel, SSE_EVENT.LOOP_CHECKING, {
426
+ taskId, loop: loopNum
427
+ });
428
+
429
+ const checkResult = await this._runTestCheck(ctx, orchestratorResult);
430
+ round.checkResult = checkResult;
431
+ round.passed = checkResult.passed;
432
+
433
+ // ── 判断是否准出通过 ──
434
+ if (checkResult.passed) {
435
+ this._log('info', `✅ 第 ${loopNum + 1} 轮准出审核通过,大循环结束`);
436
+ ctx.status = LoopStatus.PASSED;
437
+ round.endTime = nowCST();
438
+ break;
439
+ }
440
+
441
+ // ── 准出未通过,准备下一轮 ──
442
+ this._log('warn', `⚠ 第 ${loopNum + 1} 轮准出审核未通过,将进入下一轮`);
443
+
444
+ } catch (err) {
445
+ round.error = err.message;
446
+ // 终止请求识别:ABORTED_ERROR(引擎检查点)或 SkillAbortedError
447
+ // (Brain/TestCheck 的 SKILL 轮询检测到 abortSignal 抛出)。
448
+ // 两者都必须跳出循环,不能当作普通轮次异常进入下一轮重规划。
449
+ if (err instanceof ABORTED_ERROR || err?.name === 'SkillAbortedError' || this.isAborted(taskId)) {
450
+ // 终止请求:同步 ctx.aborted 标志,直接跳出循环
451
+ ctx.aborted = true;
452
+ this._log('warn', `第 ${loopNum + 1} 轮被终止: ${err.message}`);
453
+ round.endTime = nowCST();
454
+ break;
455
+ }
456
+ this._log('error', `第 ${loopNum + 1} 轮执行异常: ${err.message}`);
457
+ // 单轮异常不中断整个大循环,继续下一轮(除非是 abort)
458
+ if (ctx.aborted) {
459
+ round.endTime = nowCST();
460
+ break;
461
+ }
462
+ }
463
+
464
+ round.endTime = nowCST();
465
+ ctx.currentLoop++;
466
+
467
+ this._emit(channel, SSE_EVENT.LOOP_ROUND_END, {
468
+ taskId, loop: loopNum, passed: round.passed
469
+ });
470
+
471
+ // 持久化循环状态
472
+ await this._persistLoopState(ctx);
473
+ }
474
+
475
+ // ─── 循环结束,判定最终状态 ───
476
+ if (ctx.aborted) {
477
+ ctx.status = LoopStatus.ABORTED;
478
+ this._emit(channel, SSE_EVENT.LOOP_ABORTED, { taskId });
479
+ } else {
480
+ const lastRound = ctx.rounds[ctx.rounds.length - 1];
481
+ if (lastRound && lastRound.passed) {
482
+ ctx.status = LoopStatus.PASSED;
483
+ } else {
484
+ ctx.status = LoopStatus.FAILED;
485
+ ctx.error = `达到最大循环次数 ${ctx.maxLoops},准出审核仍未通过`;
486
+ }
487
+ }
488
+
489
+ } catch (err) {
490
+ ctx.status = LoopStatus.ERROR;
491
+ ctx.error = err.message;
492
+ this._emit(channel, SSE_EVENT.LOOP_ERROR, { taskId, error: err.message });
493
+ this._log('error', `大循环异常: ${err.message}`);
494
+ }
495
+
496
+ ctx.endTime = nowCST();
497
+
498
+ // 持久化最终状态
499
+ await this._persistLoopState(ctx);
500
+
501
+ this._emit(channel, SSE_EVENT.LOOP_COMPLETE, {
502
+ taskId,
503
+ status: ctx.status,
504
+ totalRounds: ctx.rounds.length,
505
+ passed: ctx.status === LoopStatus.PASSED
506
+ });
507
+
508
+ this._log('info', `═══ 大循环结束: status=${ctx.status}, rounds=${ctx.rounds.length} ═══`);
509
+
510
+ return this._buildLoopResult(ctx);
511
+ }
512
+
513
+ /**
514
+ * 终止大循环。
515
+ *
516
+ * 终止语义(同步返回,异步生效):
517
+ * 1. 置位终止标志(isAborted 立即为 true,供各阶段轮询检查点退出)
518
+ * 2. 终止 Orchestrator(通知运行中的 FlexRunner abortSession)
519
+ * 3. 远程中止 Brain / TestCheck 正在运行的 opencode session(尽力而为)
520
+ *
521
+ * 各阶段的 SKILL 轮询(FlexRunner._pollSession / SkillExecutor.pollSession /
522
+ * TestCheck._pollSession)在下一个轮询周期检测到标志后抛出终止并退出。
523
+ *
524
+ * @param {string} taskId
525
+ * @returns {Promise<boolean>} 是否找到了运行中的循环(true = 终止请求已受理)
526
+ */
527
+ async abortLoop(taskId) {
528
+ const ctx = this._loops.get(taskId);
529
+ if (!ctx) {
530
+ return false;
531
+ }
532
+
533
+ // 1. 置位终止标志(各阶段轮询检查点据此退出)
534
+ ctx.aborted = true;
535
+ const abortState = this._getAbortState(taskId);
536
+ abortState.aborted = true;
537
+
538
+ // 1b. 进入"中止中"过渡态:前端据此显示「任务中止中」,终止按钮置灰。
539
+ // 各阶段轮询周期(秒级)检测到标志后退出,startLoop 收尾时置为终态 ABORTED。
540
+ // 仅在仍处于活跃阶段时切换(已 PASSED/FAILED 的循环不回退状态)。
541
+ const ACTIVE_STATES = [LoopStatus.PLANNING, LoopStatus.EXECUTING, LoopStatus.CHECKING];
542
+ if (ACTIVE_STATES.includes(ctx.status)) {
543
+ ctx.status = LoopStatus.ABORTING;
544
+ this._emit(taskId, SSE_EVENT.LOOP_ABORTING, { taskId });
545
+ // 立即落盘,防止重启后过渡态丢失(恢复流程会把 aborting 视为活跃态)
546
+ await this._persistLoopState(ctx).catch(() => {});
547
+ }
548
+
549
+ // 2. 终止 Orchestrator(运行中的步骤由 FlexRunner abortSession 远程中止)
550
+ try {
551
+ const orch = this.orchestratorController._getOrchestrator(taskId);
552
+ if (orch) {
553
+ await orch.abort();
554
+ }
555
+ } catch {
556
+ // Orchestrator 可能不存在或已结束
557
+ }
558
+
559
+ // 3. 远程中止 Brain / TestCheck 阶段正在运行的 opencode session
560
+ // (否则 session 会在 opencode serve 后台继续跑到自然完成)
561
+ const pendingSession = this._consumePendingSession(taskId);
562
+ if (pendingSession) {
563
+ await this._abortSessions([pendingSession]);
564
+ }
565
+
566
+ this._log('warn', `⏹ 大循环终止已请求: taskId=${taskId}`);
567
+ return true;
568
+ }
569
+
570
+ /**
571
+ * 获取循环进度。
572
+ * @param {string} taskId
573
+ * @returns {object|null}
574
+ */
575
+ getProgress(taskId) {
576
+ const ctx = this._loops.get(taskId);
577
+ if (!ctx) return null;
578
+
579
+ return {
580
+ taskId: ctx.taskId,
581
+ status: ctx.status,
582
+ currentLoop: ctx.currentLoop,
583
+ maxLoops: ctx.maxLoops,
584
+ startTime: ctx.startTime,
585
+ endTime: ctx.endTime,
586
+ error: ctx.error,
587
+ rounds: ctx.rounds.map(r => {
588
+ const plan = r.planningResult?.plan;
589
+ const analysis = plan?.analysis || {};
590
+ const taskDef = r.planningResult?.taskDef || plan?.taskDef || {};
591
+ return {
592
+ loop: r.loop,
593
+ passed: r.passed,
594
+ startTime: r.startTime,
595
+ endTime: r.endTime,
596
+ error: r.error,
597
+ // ── Brain 拆解摘要 ──
598
+ planningPlanId: plan?.planId || null,
599
+ planningIsReplan: r.planningResult?.isReplan ?? false,
600
+ planningStepCount: taskDef.steps?.length ?? 0,
601
+ planningIntent: analysis.intent || null,
602
+ planningDomain: analysis.domain || null,
603
+ planningComplexity: analysis.complexity || null,
604
+ planningFailureAnalysis: analysis.failureAnalysis || null,
605
+ planningAdjustmentStrategy: analysis.adjustmentStrategy || null,
606
+ // ── Orchestrator 编排摘要 ──
607
+ orchestratorStatus: r.orchestratorResult?.status || null,
608
+ // ── TestCheck 审核摘要 ──
609
+ checkSummary: r.checkResult ? r.checkResult.summary : null,
610
+ checkPassed: r.checkResult ? r.checkResult.passed : null,
611
+ checkStatus: r.checkResult ? r.checkResult.status : null,
612
+ checkId: r.checkResult ? r.checkResult.checkId : null,
613
+ checkSessionId: r.checkResult ? r.checkResult.sessionId || null : null
614
+ };
615
+ })
616
+ };
617
+ }
618
+
619
+ // ─── 内部:各阶段执行 ──────────────────────────────────────────────
620
+
621
+ /**
622
+ * 执行 Brain 阶段 — 首轮拆解或后续轮重规划。
623
+ *
624
+ * 首轮(loop=0):调用 Brain.planTask() 进行初始拆解
625
+ * 后续轮(loop>0):调用 Brain.replanTask() 基于上轮审核结果重新生成目标
626
+ *
627
+ * @param {LoopContext} ctx
628
+ * @param {object} originalParams - 原始启动参数
629
+ * @returns {Promise<{plan: object, taskDef: object, isReplan: boolean}>}
630
+ * @private
631
+ */
632
+ async _runBrain(ctx, originalParams) {
633
+ const brain = this.brainController._getBrain();
634
+
635
+ // 入口终止检查:拆解开始前已请求终止 → 不启动 Brain 的 opencode session
636
+ if (this.isAborted(ctx.taskId)) {
637
+ throw new ABORTED_ERROR('Brain 拆解前已请求终止');
638
+ }
639
+
640
+ if (ctx.currentLoop === 0) {
641
+ // ── 首轮:初始拆解 ──
642
+ this._log('info', `Brain 初始拆解: taskId=${ctx.taskId}`);
643
+
644
+ const plan = await brain.planTask(ctx.taskDescription, {
645
+ taskId: ctx.taskId,
646
+ taskDir: ctx.taskDir,
647
+ skillDirectory: ctx.skillDirectory,
648
+ acceptanceCriteria: ctx.userAcceptanceCriteria,
649
+ // 终止信号回调:Brain 内部 SKILL 轮询定期检查,置 true 时抛终止退出
650
+ isAborted: () => this.isAborted(ctx.taskId),
651
+ // Session 创建回调:登记到 AbortState 供 abortLoop 远程中止
652
+ onSessionCreated: (sessionId) => this._trackSession(ctx.taskId, sessionId)
653
+ });
654
+
655
+ // 拆解期间收到终止请求(Session 已被远程中止,Brain 自然失败/退出)
656
+ if (this.isAborted(ctx.taskId)) {
657
+ throw new ABORTED_ERROR('Brain 拆解被终止');
658
+ }
659
+
660
+ this._trackSession(ctx.taskId, null);
661
+
662
+ return { plan, taskDef: plan.taskDef, isReplan: false };
663
+
664
+ } else {
665
+ // ── 后续轮:重规划 ──
666
+ const lastRound = ctx.rounds[ctx.rounds.length - 1];
667
+ const lastCheckResult = lastRound ? lastRound.checkResult : null;
668
+ const lastOrchResult = lastRound ? lastRound.orchestratorResult : null;
669
+
670
+ this._log('info', `Brain 重规划: taskId=${ctx.taskId}, loop=${ctx.currentLoop}`);
671
+
672
+ const plan = await brain.replanTask(ctx.taskDescription, {
673
+ taskId: ctx.taskId,
674
+ taskDir: ctx.taskDir,
675
+ skillDirectory: ctx.skillDirectory,
676
+ acceptanceCriteria: ctx.userAcceptanceCriteria,
677
+ previousCheckResult: lastCheckResult,
678
+ previousOrchestratorResult: lastOrchResult,
679
+ loopCount: ctx.currentLoop,
680
+ // 终止信号回调:重规划轮同样接入终止检查
681
+ isAborted: () => this.isAborted(ctx.taskId),
682
+ onSessionCreated: (sessionId) => this._trackSession(ctx.taskId, sessionId)
683
+ });
684
+
685
+ // 拆解期间收到终止请求(Session 已被远程中止,Brain 自然失败/退出)
686
+ if (this.isAborted(ctx.taskId)) {
687
+ throw new ABORTED_ERROR('Brain 重规划被终止');
688
+ }
689
+
690
+ this._trackSession(ctx.taskId, null);
691
+
692
+ return { plan, taskDef: plan.taskDef, isReplan: true };
693
+ }
694
+ }
695
+
696
+ /**
697
+ * 执行 Orchestrator 阶段 — 加载任务并等待执行完成。
698
+ *
699
+ * 通过 BrainController 的 _invokeController 模式调用 OrchestratorController:
700
+ * 1. load — 加载任务定义
701
+ * 2. start — 启动编排
702
+ * 3. 等待编排完成(轮询状态)
703
+ *
704
+ * @param {LoopContext} ctx
705
+ * @param {object} planningResult - Brain 阶段返回的 { plan, taskDef }
706
+ * @returns {Promise<object>} Orchestrator 执行结果摘要
707
+ * @private
708
+ */
709
+ async _runOrchestrator(ctx, planningResult) {
710
+ const { taskDef } = planningResult;
711
+ const orchController = this.orchestratorController;
712
+
713
+ // 入口终止检查:Brain 完成后终止请求已到位 → 不加载编排(否则会完整跑一轮新编排)
714
+ if (this.isAborted(ctx.taskId)) {
715
+ throw new ABORTED_ERROR('编排执行前已请求终止');
716
+ }
717
+
718
+ // ── 加载任务 ──
719
+ const loadReq = { body: taskDef };
720
+ const loadRes = this._createMockResponse();
721
+ await orchController.load(loadReq, loadRes);
722
+
723
+ if (loadRes._status >= 400) {
724
+ throw new Error(`Orchestrator 加载失败: ${loadRes._data?.error || loadRes._data?.message}`);
725
+ }
726
+
727
+ // 加载后终止检查:终止请求在 load 过程中到达 → 不启动编排
728
+ if (this.isAborted(ctx.taskId)) {
729
+ throw new ABORTED_ERROR('编排启动前已请求终止');
730
+ }
731
+
732
+ // ── 启动编排 ──
733
+ const startReq = { body: { taskId: ctx.taskId } };
734
+ const startRes = this._createMockResponse();
735
+ await orchController.start(startReq, startRes);
736
+
737
+ if (startRes._status >= 400) {
738
+ throw new Error(`Orchestrator 启动失败: ${startRes._data?.error || startRes._data?.message}`);
739
+ }
740
+
741
+ // ── 等待编排完成 ──
742
+ const orch = orchController._getOrchestrator(ctx.taskId);
743
+ if (!orch) {
744
+ throw new Error(`Orchestrator 实例未找到: taskId=${ctx.taskId}`);
745
+ }
746
+
747
+ // Orchestrator.start() 是异步执行的(Controller 中 fire-and-forget)
748
+ // 需要轮询等待编排状态变为终态
749
+ const result = await this._waitForOrchestrator(ctx.taskId, orch);
750
+
751
+ return result;
752
+ }
753
+
754
+ /**
755
+ * 等待 Orchestrator 执行完成。
756
+ *
757
+ * 轮询编排状态,直到进入终态(COMPLETED / FAILED / ABORTED / DEADLOCKED)。
758
+ *
759
+ * @param {string} taskId
760
+ * @param {object} orch - Orchestrator 实例
761
+ * @returns {Promise<object>} 编排结果摘要
762
+ * @private
763
+ */
764
+ async _waitForOrchestrator(taskId, orch) {
765
+ const POLL_MS = 5000;
766
+ const TIMEOUT_MS = 120 * 60 * 1000; // 120 分钟超时
767
+ const deadline = Date.now() + TIMEOUT_MS;
768
+
769
+ while (Date.now() < deadline) {
770
+ const ctx = this._loops.get(taskId);
771
+ if (ctx && ctx.aborted) {
772
+ break;
773
+ }
774
+
775
+ await this._sleep(POLL_MS);
776
+
777
+ const status = orch.status;
778
+ // 终态判断
779
+ if (status === 'completed' || status === 'failed' ||
780
+ status === 'aborted' || status === 'deadlocked') {
781
+ break;
782
+ }
783
+ }
784
+
785
+ const progress = orch.getProgress();
786
+ this._log('info', `Orchestrator 执行结束: status=${progress.status}`);
787
+
788
+ return {
789
+ status: progress.status,
790
+ taskId: progress.taskId,
791
+ stats: progress.stats,
792
+ steps: progress.steps,
793
+ error: progress.error,
794
+ startTime: progress.startTime,
795
+ endTime: progress.endTime
796
+ };
797
+ }
798
+
799
+ /**
800
+ * 执行 TestCheck 阶段 — 准出审核。
801
+ *
802
+ * @param {LoopContext} ctx
803
+ * @param {object} orchestratorResult - Orchestrator 执行结果
804
+ * @returns {Promise<object>} CheckResult
805
+ * @private
806
+ */
807
+ async _runTestCheck(ctx, orchestratorResult) {
808
+ const testCheck = this._getTestCheck();
809
+
810
+ // 入口终止检查:编排结束后终止请求已到位 → 不启动审核 session
811
+ if (this.isAborted(ctx.taskId)) {
812
+ throw new ABORTED_ERROR('准出审核前已请求终止');
813
+ }
814
+
815
+ // 从 Brain plan 中提取验收标准
816
+ const lastPlan = ctx.rounds.length > 0
817
+ ? ctx.rounds[ctx.rounds.length - 1].planningResult?.plan
818
+ : null;
819
+
820
+ // 优先使用当前轮的 plan,否则从 Brain 加载
821
+ let acceptanceCriteria = [];
822
+ if (lastPlan && lastPlan.analysis && lastPlan.analysis.acceptanceCriteria) {
823
+ acceptanceCriteria = lastPlan.analysis.acceptanceCriteria;
824
+ } else {
825
+ // 从持久化加载 brain.json
826
+ try {
827
+ const brain = this.brainController._getBrain();
828
+ const plan = await brain.loadPlan('', { taskId: ctx.taskId });
829
+ if (plan && plan.analysis && plan.analysis.acceptanceCriteria) {
830
+ acceptanceCriteria = plan.analysis.acceptanceCriteria;
831
+ }
832
+ } catch {
833
+ // 加载失败使用空标准
834
+ }
835
+ }
836
+
837
+ const result = await testCheck.runCheck({
838
+ taskId: ctx.taskId,
839
+ taskDir: ctx.taskDir,
840
+ taskDescription: ctx.taskDescription,
841
+ acceptanceCriteria,
842
+ orchestratorResult,
843
+ loopCount: ctx.currentLoop,
844
+ skillDirectory: ctx.skillDirectory,
845
+ // 终止信号回调:审核 SKILL 轮询定期检查,置 true 时抛终止退出
846
+ isAborted: () => this.isAborted(ctx.taskId),
847
+ // Session 创建回调:登记到 AbortState 供 abortLoop 远程中止
848
+ onSessionCreated: (sessionId) => this._trackSession(ctx.taskId, sessionId)
849
+ });
850
+
851
+ // 审核期间收到终止请求(session 已被远程中止)
852
+ if (this.isAborted(ctx.taskId)) {
853
+ this._trackSession(ctx.taskId, null);
854
+ throw new ABORTED_ERROR('准出审核被终止');
855
+ }
856
+
857
+ this._trackSession(ctx.taskId, null);
858
+
859
+ return result;
860
+ }
861
+
862
+ // ─── 内部:持久化 ──────────────────────────────────────────────────
863
+
864
+ /**
865
+ * 持久化大循环状态到 db/task/{taskId}/loop.json。
866
+ *
867
+ * 文件布局:
868
+ * db/task/{taskId}/
869
+ * ├── brain.json ← Brain 拆解计划(最新快照)
870
+ * ├── brain_loop0.json ← 各轮 Brain 计划(独立保留)
871
+ * ├── brain_loop1.json
872
+ * ├── orchestration.json ← Orchestrator 编排状态(最新快照)
873
+ * ├── orchestration_loop0.json ← 各轮编排状态(独立保留)
874
+ * ├── orchestration_loop1.json
875
+ * ├── stepid_1_loop0.json ← 各轮步骤执行记录(独立保留)
876
+ * ├── stepid_1_loop1.json
877
+ * ├── stepId1_loop0_result_0.md ← 各轮步骤结果文件(独立保留)
878
+ * ├── stepId1_loop1_result_0.md
879
+ * ├── loop.json ← LoopEngine 大循环状态(本方法写入)
880
+ * ├── testcheck_loop0.json ← 各轮审核结果
881
+ * └── ...
882
+ *
883
+ * @param {LoopContext} ctx
884
+ * @private
885
+ */
886
+ async _persistLoopState(ctx) {
887
+ try {
888
+ const json = this.storage.json('task');
889
+ const state = {
890
+ taskId: ctx.taskId,
891
+ taskDescription: ctx.taskDescription,
892
+ userAcceptanceCriteria: ctx.userAcceptanceCriteria,
893
+ taskDir: ctx.taskDir,
894
+ skillDirectory: ctx.skillDirectory,
895
+ maxLoops: ctx.maxLoops,
896
+ currentLoop: ctx.currentLoop,
897
+ status: ctx.status,
898
+ startTime: ctx.startTime,
899
+ endTime: ctx.endTime,
900
+ error: ctx.error,
901
+ aborted: ctx.aborted,
902
+ rounds: ctx.rounds.map(r => {
903
+ const plan = r.planningResult?.plan;
904
+ const analysis = plan?.analysis || {};
905
+ const taskDef = r.planningResult?.taskDef || plan?.taskDef || {};
906
+ return {
907
+ loop: r.loop,
908
+ startTime: r.startTime,
909
+ endTime: r.endTime,
910
+ passed: r.passed,
911
+ error: r.error,
912
+ // ── Brain 拆解摘要 ──
913
+ planningPlanId: plan?.planId || null,
914
+ planningIsReplan: r.planningResult?.isReplan ?? false,
915
+ planningLoopCount: plan?.loopCount ?? r.loop,
916
+ planningStepCount: taskDef.steps?.length ?? 0,
917
+ planningIntent: analysis.intent || null,
918
+ planningDomain: analysis.domain || null,
919
+ planningComplexity: analysis.complexity || null,
920
+ planningFailureAnalysis: analysis.failureAnalysis || null,
921
+ planningAdjustmentStrategy: analysis.adjustmentStrategy || null,
922
+ planningReplanInfo: taskDef.replanInfo || null,
923
+ // ── Orchestrator 编排摘要 ──
924
+ orchestratorStatus: r.orchestratorResult?.status || null,
925
+ // ── TestCheck 审核摘要 ──
926
+ checkPassed: r.checkResult?.passed ?? null,
927
+ checkStatus: r.checkResult?.status || null,
928
+ checkSummary: r.checkResult?.summary || null,
929
+ checkId: r.checkResult?.checkId || null,
930
+ checkSessionId: r.checkResult?.sessionId || null
931
+ };
932
+ }),
933
+ persistedAt: nowCST()
934
+ };
935
+ await json.write(`${ctx.taskId}/loop`, state);
936
+ } catch (err) {
937
+ this._log('warn', `持久化大循环状态失败: ${err.message}`);
938
+ }
939
+ }
940
+
941
+ /**
942
+ * 从持久化文件恢复大循环状态。
943
+ *
944
+ * @param {string} taskId
945
+ * @returns {Promise<LoopContext|null>}
946
+ */
947
+ async restoreLoop(taskId) {
948
+ try {
949
+ const json = this.storage.json('task');
950
+ const state = await json.read(`${taskId}/loop`);
951
+ if (!state) return null;
952
+
953
+ /** @type {LoopContext} */
954
+ const ctx = {
955
+ taskId: state.taskId,
956
+ taskDescription: state.taskDescription || '',
957
+ userAcceptanceCriteria: state.userAcceptanceCriteria || '',
958
+ taskDir: state.taskDir || '',
959
+ skillDirectory: state.skillDirectory || '',
960
+ maxLoops: state.maxLoops || DEFAULT_MAX_LOOPS,
961
+ currentLoop: state.currentLoop || 0,
962
+ // 恢复时 aborting(中止中的过渡态)归一化为 aborted:
963
+ // 重启后循环执行体已不存在,无法也不应继续"等待中止完成"
964
+ status: state.status === LoopStatus.ABORTING
965
+ ? LoopStatus.ABORTED
966
+ : (state.status || LoopStatus.IDLE),
967
+ startTime: state.startTime || null,
968
+ endTime: state.endTime || null,
969
+ rounds: Array.isArray(state.rounds) ? state.rounds.map(r => ({
970
+ loop: r.loop,
971
+ startTime: r.startTime,
972
+ endTime: r.endTime,
973
+ // 从持久化的摘要字段恢复 planningResult(运行时完整对象不可恢复,
974
+ // 但恢复关键元数据供 getProgress 展示)
975
+ planningResult: r.planningPlanId ? {
976
+ plan: {
977
+ planId: r.planningPlanId,
978
+ isReplan: r.planningIsReplan ?? false,
979
+ loopCount: r.planningLoopCount ?? r.loop,
980
+ analysis: {
981
+ intent: r.planningIntent || '',
982
+ domain: r.planningDomain || '',
983
+ complexity: r.planningComplexity || '',
984
+ failureAnalysis: r.planningFailureAnalysis || '',
985
+ adjustmentStrategy: r.planningAdjustmentStrategy || ''
986
+ },
987
+ taskDef: { replanInfo: r.planningReplanInfo || null }
988
+ },
989
+ taskDef: null,
990
+ isReplan: r.planningIsReplan ?? false
991
+ } : null,
992
+ orchestratorResult: r.orchestratorStatus ? { status: r.orchestratorStatus } : null,
993
+ checkResult: r.checkId ? {
994
+ checkId: r.checkId,
995
+ passed: r.checkPassed ?? false,
996
+ status: r.checkStatus || CheckStatus.IDLE,
997
+ summary: r.checkSummary || '',
998
+ sessionId: r.checkSessionId || ''
999
+ } : null,
1000
+ passed: r.passed || false,
1001
+ error: r.error || null
1002
+ })) : [],
1003
+ error: state.error || null,
1004
+ aborted: false
1005
+ };
1006
+
1007
+ this._loops.set(taskId, ctx);
1008
+
1009
+ // ── 补充加载:旧版 loop.json 缺少 checkSessionId,
1010
+ // 从 testcheck_loopN.json 中补全各轮 checkResult.sessionId ──
1011
+ for (const round of ctx.rounds) {
1012
+ if (round.checkResult && !round.checkResult.sessionId) {
1013
+ try {
1014
+ const checkData = await json.read(`${taskId}/testcheck_loop${round.loop}`);
1015
+ if (checkData && checkData.sessionId) {
1016
+ round.checkResult.sessionId = checkData.sessionId;
1017
+ }
1018
+ } catch {
1019
+ // testcheck_loopN.json 可能不存在(审核未执行),跳过
1020
+ }
1021
+ }
1022
+ }
1023
+ return ctx;
1024
+ } catch {
1025
+ return null;
1026
+ }
1027
+ }
1028
+
1029
+ // ─── 内部工具 ──────────────────────────────────────────────────────
1030
+
1031
+ /**
1032
+ * 构建最终的 LoopResult 返回值。
1033
+ * @param {LoopContext} ctx
1034
+ * @returns {LoopResult}
1035
+ * @private
1036
+ */
1037
+ _buildLoopResult(ctx) {
1038
+ const lastPassedRound = ctx.rounds.find(r => r.passed);
1039
+
1040
+ return {
1041
+ taskId: ctx.taskId,
1042
+ status: ctx.status,
1043
+ passed: ctx.status === LoopStatus.PASSED,
1044
+ totalRounds: ctx.rounds.length,
1045
+ currentLoop: ctx.currentLoop,
1046
+ maxLoops: ctx.maxLoops,
1047
+ startTime: ctx.startTime,
1048
+ endTime: ctx.endTime,
1049
+ error: ctx.error,
1050
+ rounds: ctx.rounds.map(r => ({
1051
+ loop: r.loop,
1052
+ passed: r.passed,
1053
+ startTime: r.startTime,
1054
+ endTime: r.endTime,
1055
+ error: r.error,
1056
+ planId: r.planningResult?.plan?.planId || null,
1057
+ orchestratorStatus: r.orchestratorResult?.status || null,
1058
+ checkPassed: r.checkResult?.passed ?? null,
1059
+ checkSummary: r.checkResult?.summary || null,
1060
+ improvementSuggestions: r.checkResult?.improvementSuggestions || []
1061
+ })),
1062
+ finalCheckResult: lastPassedRound
1063
+ ? lastPassedRound.checkResult
1064
+ : (ctx.rounds[ctx.rounds.length - 1]?.checkResult || null)
1065
+ };
1066
+ }
1067
+
1068
+ /**
1069
+ * 创建模拟的 ResponseContext(用于调用 Controller 方法)。
1070
+ * @returns {object}
1071
+ * @private
1072
+ */
1073
+ _createMockResponse() {
1074
+ return {
1075
+ _data: null,
1076
+ _status: 200,
1077
+ json(data, status = 200) {
1078
+ this._data = data;
1079
+ this._status = status;
1080
+ },
1081
+ error(status, message) {
1082
+ this._data = { error: message };
1083
+ this._status = status;
1084
+ }
1085
+ };
1086
+ }
1087
+
1088
+ /**
1089
+ * Promise 版 sleep。
1090
+ * @param {number} ms
1091
+ * @returns {Promise<void>}
1092
+ * @private
1093
+ */
1094
+ _sleep(ms) {
1095
+ return new Promise((resolve) => setTimeout(resolve, ms));
1096
+ }
1097
+
1098
+ /**
1099
+ * 统一日志输出。
1100
+ * @param {'info'|'warn'|'error'} level
1101
+ * @param {string} message
1102
+ * @private
1103
+ */
1104
+ _log(level, message) {
1105
+ const prefix = '[LoopEngine]';
1106
+ const ts = nowCST();
1107
+ switch (level) {
1108
+ case 'error':
1109
+ console.error(`${ts} ${prefix} ❌ ${message}`);
1110
+ break;
1111
+ case 'warn':
1112
+ console.warn(`${ts} ${prefix} ⚠ ${message}`);
1113
+ break;
1114
+ default:
1115
+ console.info(`${ts} ${prefix} ℹ ${message}`);
1116
+ }
1117
+ }
1118
+ }
1119
+
1120
+ // ─── 辅助函数 ──────────────────────────────────────────────────────────────
1121
+
1122
+ /**
1123
+ * 生成 taskId。
1124
+ * @returns {string}
1125
+ */
1126
+ function _generateTaskId() {
1127
+ const ts = Date.now().toString(36);
1128
+ const rand = Math.random().toString(36).substring(2, 6);
1129
+ return `task_${ts}${rand}`;
1130
+ }
1131
+
1132
+ export { LoopEngine, ABORTED_ERROR };
1133
+ export default LoopEngine;