@ohos-cpf/3rdloop 0.0.2 → 0.0.4

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 +290 -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,1125 +1,1125 @@
1
- import { FlexRunner, ControlType } from '../../FlexRunner/FlexRunner.js';
2
- import { OrchestratorStatus } from '../../Orchestrator/Orchestrator.js';
3
- import { nowCST } from '../../library/cstTime.js';
4
- import { createZipBuffer } from '../library/zipArchive.js';
5
- import fs from 'node:fs/promises';
6
- import path from 'node:path';
7
-
8
- /**
9
- * FlexRunnerController —— FlexRunner 执行控制器
10
- *
11
- * 职责:封装 FlexRunner 的 HTTP 接口,供前端测试和操作。
12
- * 不感知 HTTP 层细节,只接收 RequestContext / ResponseContext。
13
- *
14
- * 管理多个 FlexRunner 实例(按 taskId + stepId 区分),支持:
15
- * - 初始化任务
16
- * - 执行单步(executeStart)
17
- * - 查询状态
18
- * - 控制执行(暂停/继续/终止)
19
- * - 获取实时消息
20
- * - 获取会话内容
21
- * - 检查结果文件
22
- * - 列出任务步骤记录
23
- */
24
- class FlexRunnerController {
25
- /**
26
- * @param {object} [deps] - 共享依赖
27
- * @param {object} [deps.orchestratorController] - OrchestratorController 实例(用于从编排器中查找 FlexRunner)
28
- * @param {object} [deps.storage] - Storage 实例(用于在没有活跃 runner 时直接读取 session 归档)
29
- */
30
- constructor(deps = {}) {
31
- /** @type {Map<string, FlexRunner>} key = `${taskId}::${stepId}` */
32
- this._runners = new Map();
33
- /** @type {Map<string, {logs:string[], status:string}>} key = taskId */
34
- this._taskLogs = new Map();
35
- /** @type {object|null} OrchestratorController 实例 */
36
- this._orchestratorController = deps.orchestratorController || null;
37
- /** @type {object|null} Storage 实例 */
38
- this._storage = deps.storage || null;
39
- }
40
-
41
- // ─── 辅助方法 ──────────────────────────────────────────────────────
42
-
43
- /**
44
- * 生成实例缓存 key。
45
- */
46
- _key(taskId, stepId) {
47
- return `${taskId}::${stepId}`;
48
- }
49
-
50
- /**
51
- * 获取已有实例,不存在则报错。
52
- *
53
- * 查找顺序:
54
- * 1. 本地 _runners Map(通过 /init 注册的实例)
55
- * 2. OrchestratorController 中编排器步骤节点的 _runner(编排执行时创建的实例)
56
- */
57
- _getRunner(taskId, stepId) {
58
- // 1. 本地 Map
59
- const key = this._key(taskId, stepId);
60
- const localRunner = this._runners.get(key);
61
- if (localRunner) return localRunner;
62
-
63
- // 2. 从 OrchestratorController 的编排器实例中查找
64
- if (this._orchestratorController) {
65
- const runner = this._orchestratorController.getStepRunner(taskId, stepId);
66
- if (runner) return runner;
67
- }
68
-
69
- throw new Error(`未找到任务实例: taskId=${taskId}, stepId=${stepId},请先初始化或执行编排`);
70
- }
71
-
72
- /**
73
- * 在没有活跃 FlexRunner 实例时,从编排器步骤元信息中提取 sessionId,
74
- * 然后直接读取本地 session 归档返回消息列表。
75
- *
76
- * 用于服务重启后(runner 不在内存中)仍能查看已完成步骤的对话记录。
77
- *
78
- * @param {string} taskId
79
- * @param {number} stepId
80
- * @param {number} [loop] - 大循环轮次(0-based)。指定时从磁盘 orchestration_loop{N}.json 读取历史轮次步骤信息
81
- * @returns {Promise<{sessionId:string, messages:Array, meta:object|null}|null>}
82
- */
83
- async _readSessionArchiveFromStep(taskId, stepId, loop) {
84
- if (!this._orchestratorController || !this._storage) return null;
85
-
86
- // 指定轮次:从磁盘读取历史轮次的步骤信息
87
- let stepInfo;
88
- if (loop != null) {
89
- stepInfo = await this._orchestratorController.getStepInfoByLoop(taskId, stepId, loop);
90
- } else {
91
- stepInfo = this._orchestratorController.getStepInfo(taskId, stepId);
92
- }
93
- if (!stepInfo || !stepInfo.sessionId) return null;
94
-
95
- const sessionId = stepInfo.sessionId;
96
- const sessionStore = this._storage.sessionStore();
97
-
98
- const [meta, messages] = await Promise.all([
99
- sessionStore.readMeta(sessionId).catch(() => null),
100
- sessionStore.readMessages(sessionId).catch(() => [])
101
- ]);
102
-
103
- return { sessionId, messages, meta };
104
- }
105
-
106
- /**
107
- * 将 FlexRunner 实例的状态序列化为 JSON。
108
- */
109
- _runnerToJSON(runner) {
110
- return {
111
- taskId: runner.taskId,
112
- stepId: runner.stepId,
113
- dependId: runner.dependId,
114
- nextStepId: runner.nextStepId,
115
- taskDescription: runner.taskDescription,
116
- taskDir: runner.taskDir,
117
- skillDirectory: runner.skillDirectory,
118
- inputReport: runner.inputReport,
119
- maxRetries: runner.maxRetries,
120
- executeStep: runner.executeStep,
121
- retryTimes: runner.retryTimes,
122
- sessionId: runner.sessionId,
123
- startTime: runner.startTime,
124
- endTime: runner.endTime,
125
- resultPath: runner.resultPath,
126
- initialized: runner.initialized,
127
- canRetry: runner.canRetry(),
128
- };
129
- }
130
-
131
- // ─── HTTP Handlers ─────────────────────────────────────────────────
132
-
133
- /**
134
- * POST /api/flexrunner/init
135
- * 初始化一个 FlexRunner 实例
136
- *
137
- * Body: {
138
- * taskId, stepId, dependId, nextStepId, failStepId, taskDescription,
139
- * skillDirectory, taskDir, inputReport, maxRetries
140
- * }
141
- */
142
- async init(req, res) {
143
- const body = req.body || {};
144
- const { taskId } = body;
145
-
146
- if (!taskId) {
147
- return res.error(400, 'taskId 为必填项');
148
- }
149
-
150
- const stepId = Number(body.stepId);
151
- if (!Number.isInteger(stepId) || stepId <= 0) {
152
- return res.error(400, 'stepId 必须为正整数');
153
- }
154
-
155
- // 如果同 key 实例已存在,覆盖(重新初始化)
156
- const runner = new FlexRunner();
157
- await runner.initContext({
158
- taskId,
159
- stepId,
160
- dependId: Number(body.dependId) || 0,
161
- nextStepId: Number(body.nextStepId) || 0,
162
- failStepId: Number(body.failStepId) || 0,
163
- taskDescription: body.taskDescription || '',
164
- skillDirectory: body.skillDirectory || '',
165
- taskDir: body.taskDir || '',
166
- inputReport: body.inputReport || '',
167
- maxRetries: typeof body.maxRetries === 'number' ? body.maxRetries : undefined,
168
- });
169
-
170
- const key = this._key(taskId, stepId);
171
- this._runners.set(key, runner);
172
-
173
- res.json({
174
- status: 'ok',
175
- message: `FlexRunner 已初始化: taskId=${taskId}, stepId=${stepId}`,
176
- runner: this._runnerToJSON(runner),
177
- });
178
- }
179
-
180
- /**
181
- * POST /api/flexrunner/execute
182
- * 执行单次 executeStart(不内部循环,由调用方决定是否重试)
183
- *
184
- * Body: { taskId, stepId }
185
- */
186
- async execute(req, res) {
187
- const body = req.body || {};
188
- const taskId = body.taskId;
189
- const stepId = Number(body.stepId);
190
-
191
- let runner;
192
- try {
193
- runner = this._getRunner(taskId, stepId);
194
- } catch (err) {
195
- return res.error(404, err.message);
196
- }
197
-
198
- if (!runner.canRetry()) {
199
- return res.error(409, `已达最大重试次数或已终止,无法继续执行`);
200
- }
201
-
202
- const result = await runner.executeStart();
203
-
204
- res.json({
205
- status: 'ok',
206
- result,
207
- runner: this._runnerToJSON(runner),
208
- });
209
- }
210
-
211
- /**
212
- * POST /api/flexrunner/execute-loop
213
- * 自动循环执行直到成功或不可重试
214
- *
215
- * Body: { taskId, stepId }
216
- */
217
- async executeLoop(req, res) {
218
- const body = req.body || {};
219
- const taskId = body.taskId;
220
- const stepId = Number(body.stepId);
221
-
222
- let runner;
223
- try {
224
- runner = this._getRunner(taskId, stepId);
225
- } catch (err) {
226
- return res.error(404, err.message);
227
- }
228
-
229
- const results = [];
230
- let lastResult;
231
-
232
- while (runner.canRetry()) {
233
- lastResult = await runner.executeStart();
234
- results.push({
235
- attempt: runner.executeStep,
236
- success: lastResult.success,
237
- nextStepId: lastResult.nextStepId,
238
- resultPath: lastResult.resultPath,
239
- });
240
-
241
- if (lastResult.success) break;
242
- }
243
-
244
- res.json({
245
- status: lastResult?.success ? 'ok' : 'error',
246
- totalAttempts: results.length,
247
- results,
248
- finalResult: lastResult,
249
- runner: this._runnerToJSON(runner),
250
- });
251
- }
252
-
253
- /**
254
- * GET /api/flexrunner/status?taskId=xxx&stepId=yyy
255
- * 查询 FlexRunner 实例状态
256
- *
257
- * 如果本地无实例,尝试从编排器恢复步骤状态后返回。
258
- */
259
- async status(req, res) {
260
- const taskId = req.query.taskId;
261
- const stepId = Number(req.query.stepId);
262
-
263
- let runner;
264
- try {
265
- runner = this._getRunner(taskId, stepId);
266
- res.json({
267
- status: 'ok',
268
- runner: this._runnerToJSON(runner),
269
- });
270
- } catch (err) {
271
- // 编排器中存在该任务但步骤已结束(runner 未存活),返回步骤元信息
272
- if (this._orchestratorController) {
273
- const stepInfo = this._orchestratorController.getStepInfo(taskId, stepId);
274
- if (stepInfo) {
275
- return res.json({
276
- status: 'ok',
277
- runner: { ...stepInfo, initialized: false },
278
- });
279
- }
280
- }
281
- return res.error(404, err.message);
282
- }
283
- }
284
-
285
- /**
286
- * GET /api/flexrunner/live-messages?taskId=xxx&stepId=yyy[&loop=N]
287
- * 获取当前会话的实时对话消息
288
- *
289
- * - 不传 loop:从内存编排器实例查找(最新轮次)
290
- * - 传 loop=N:从磁盘 orchestration_loop{N}.json 读取历史轮次步骤的 sessionId,再读取本地归档
291
- */
292
- async liveMessages(req, res) {
293
- const taskId = req.query.taskId;
294
- const stepId = Number(req.query.stepId);
295
- const loop = req.query.loop != null && req.query.loop !== '' ? Number(req.query.loop) : undefined;
296
-
297
- // 指定历史轮次:直接从磁盘读取该轮次的步骤归档(不查内存最新实例)
298
- if (loop != null) {
299
- const archive = await this._readSessionArchiveFromStep(taskId, stepId, loop);
300
- if (archive) {
301
- return res.json({
302
- status: 'ok',
303
- sessionId: archive.sessionId,
304
- count: archive.messages.length,
305
- messages: archive.messages,
306
- });
307
- }
308
- return res.error(404, `未找到第 ${loop} 轮步骤归档: taskId=${taskId}, stepId=${stepId}`);
309
- }
310
-
311
- let runner;
312
- try {
313
- runner = this._getRunner(taskId, stepId);
314
- } catch (err) {
315
- // 没有活跃 runner(如服务重启后从磁盘恢复的任务),
316
- // 尝试从编排器步骤元信息中获取 sessionId 并读取本地归档
317
- const archive = await this._readSessionArchiveFromStep(taskId, stepId);
318
- if (archive) {
319
- return res.json({
320
- status: 'ok',
321
- sessionId: archive.sessionId,
322
- count: archive.messages.length,
323
- messages: archive.messages,
324
- });
325
- }
326
- return res.error(404, err.message);
327
- }
328
-
329
- // 有活跃 runner,优先从远端拉取实时消息
330
- try {
331
- const messages = await runner.getLiveMessages();
332
- return res.json({
333
- status: 'ok',
334
- sessionId: runner.sessionId,
335
- count: messages.length,
336
- messages,
337
- });
338
- } catch (liveErr) {
339
- // 远端不可用(opencode 服务关闭/session 过期),回退到本地归档
340
- const sessionId = runner.sessionId;
341
- if (sessionId && this._storage) {
342
- const sessionStore = this._storage.sessionStore();
343
- const messages = await sessionStore.readMessages(sessionId).catch(() => []);
344
- return res.json({
345
- status: 'ok',
346
- sessionId,
347
- count: messages.length,
348
- messages,
349
- warning: '远端不可用,已回退到本地归档',
350
- });
351
- }
352
- return res.error(500, `获取实时消息失败: ${liveErr.message}`);
353
- }
354
- }
355
-
356
- /**
357
- * POST /api/flexrunner/inject-prompt
358
- * 向当前会话注入提示词/上下文(不触发 AI 回复)
359
- *
360
- * Body: { taskId, stepId, message, [loop] }
361
- */
362
- async injectPrompt(req, res) {
363
- const body = req.body || {};
364
- const taskId = body.taskId;
365
- const stepId = Number(body.stepId);
366
- const { message } = body;
367
- const loop = body.loop != null ? Number(body.loop) : undefined;
368
-
369
- if (!message || typeof message !== 'string') {
370
- return res.error(400, 'message 为必填项且必须为字符串');
371
- }
372
-
373
- // 指定历史轮次:历史会话已结束,无法注入(仅支持查看归档)
374
- if (loop != null) {
375
- return res.error(409, `历史轮次(${loop})的会话已结束,不支持注入提示词`);
376
- }
377
-
378
- let runner;
379
- try {
380
- runner = this._getRunner(taskId, stepId);
381
- } catch (err) {
382
- return res.error(404, err.message);
383
- }
384
-
385
- const result = await runner.injectPrompt(message);
386
-
387
- res.json({
388
- status: result.success ? 'ok' : 'error',
389
- sessionId: runner.sessionId,
390
- success: result.success,
391
- message: result.success
392
- ? '提示词已注入'
393
- : '注入失败(可能无活跃会话或 CLI 不支持)',
394
- });
395
- }
396
-
397
- /**
398
- * POST /api/flexrunner/send-and-wait
399
- * 向当前会话发送消息并同步等待 AI 回复完成
400
- *
401
- * Body: { taskId, stepId, message, [loop] }
402
- */
403
- async sendAndWaitMessage(req, res) {
404
- const body = req.body || {};
405
- const taskId = body.taskId;
406
- const stepId = Number(body.stepId);
407
- const { message } = body;
408
- const loop = body.loop != null ? Number(body.loop) : undefined;
409
-
410
- if (!message || typeof message !== 'string') {
411
- return res.error(400, 'message 为必填项且必须为字符串');
412
- }
413
-
414
- // 指定历史轮次:历史会话已结束,无法发送消息
415
- if (loop != null) {
416
- return res.error(409, `历史轮次(${loop})的会话已结束,不支持发送消息`);
417
- }
418
-
419
- let runner;
420
- try {
421
- runner = this._getRunner(taskId, stepId);
422
- } catch (err) {
423
- return res.error(404, err.message);
424
- }
425
-
426
- const result = await runner.sendAndWaitMessage(message);
427
-
428
- res.json({
429
- status: result.success ? 'ok' : 'error',
430
- sessionId: runner.sessionId,
431
- success: result.success,
432
- response: result.response,
433
- content: result.content,
434
- message: result.success
435
- ? '已收到 AI 回复'
436
- : '发送失败(可能无活跃会话或 CLI 不支持)',
437
- });
438
- }
439
-
440
- /**
441
- * GET /api/flexrunner/session?taskId=xxx&stepId=yyy[&loop=N]
442
- * 获取当前会话的完整归档内容
443
- *
444
- * - 不传 loop:从内存编排器实例查找(最新轮次)
445
- * - 传 loop=N:从磁盘 orchestration_loop{N}.json 读取历史轮次步骤的 sessionId,再读取本地归档
446
- */
447
- async session(req, res) {
448
- const taskId = req.query.taskId;
449
- const stepId = Number(req.query.stepId);
450
- const loop = req.query.loop != null && req.query.loop !== '' ? Number(req.query.loop) : undefined;
451
-
452
- // 指定历史轮次:直接从磁盘读取该轮次的步骤归档
453
- if (loop != null) {
454
- const archive = await this._readSessionArchiveFromStep(taskId, stepId, loop);
455
- if (archive) {
456
- return res.json({
457
- status: 'ok',
458
- sessionId: archive.sessionId,
459
- meta: archive.meta,
460
- messages: archive.messages,
461
- });
462
- }
463
- return res.error(404, `未找到第 ${loop} 轮步骤归档: taskId=${taskId}, stepId=${stepId}`);
464
- }
465
-
466
- let runner;
467
- try {
468
- runner = this._getRunner(taskId, stepId);
469
- } catch (err) {
470
- // 没有活跃 runner,从编排器步骤元信息获取 sessionId 并读取本地归档
471
- const archive = await this._readSessionArchiveFromStep(taskId, stepId);
472
- if (archive) {
473
- return res.json({
474
- status: 'ok',
475
- sessionId: archive.sessionId,
476
- meta: archive.meta,
477
- messages: archive.messages,
478
- });
479
- }
480
- return res.error(404, err.message);
481
- }
482
-
483
- try {
484
- const jsonStr = await runner.getSessionContent();
485
- const data = JSON.parse(jsonStr);
486
-
487
- res.json({
488
- status: 'ok',
489
- sessionId: runner.sessionId,
490
- ...data,
491
- });
492
- } catch (err) {
493
- // runner 存在但读取失败,尝试直接从本地归档恢复
494
- const sessionId = runner.sessionId;
495
- if (sessionId && this._storage) {
496
- const sessionStore = this._storage.sessionStore();
497
- const [meta, messages] = await Promise.all([
498
- sessionStore.readMeta(sessionId).catch(() => null),
499
- sessionStore.readMessages(sessionId).catch(() => [])
500
- ]);
501
- return res.json({
502
- status: 'ok',
503
- sessionId,
504
- meta,
505
- messages,
506
- warning: '已回退到本地归档',
507
- });
508
- }
509
- return res.error(500, `获取会话内容失败: ${err.message}`);
510
- }
511
- }
512
-
513
- /**
514
- * POST /api/flexrunner/control
515
- * 控制执行(暂停/继续/终止)
516
- *
517
- * Body: { taskId, stepId, type: 'pause'|'resume'|'abort' }
518
- */
519
- async control(req, res) {
520
- const body = req.body || {};
521
- const taskId = body.taskId;
522
- const stepId = Number(body.stepId);
523
- const { type } = body;
524
-
525
- let runner;
526
- try {
527
- runner = this._getRunner(taskId, stepId);
528
- } catch (err) {
529
- return res.error(404, err.message);
530
- }
531
-
532
- const validTypes = ['pause', 'resume', 'abort'];
533
- if (!validTypes.includes(type)) {
534
- return res.error(400, `type 必须为: ${validTypes.join(', ')}`);
535
- }
536
-
537
- const ok = await runner.controlExecution(type);
538
-
539
- res.json({
540
- status: ok ? 'ok' : 'error',
541
- message: `控制指令 "${type}" 已${ok ? '执行' : '失败'}`,
542
- runner: this._runnerToJSON(runner),
543
- });
544
- }
545
-
546
- /**
547
- * POST /api/flexrunner/check-result
548
- * 检查结果文件的成功/失败状态
549
- *
550
- * Body: { taskId, stepId, resultFilePath }
551
- */
552
- async checkResult(req, res) {
553
- const body = req.body || {};
554
- const taskId = body.taskId;
555
- const stepId = Number(body.stepId);
556
- const { resultFilePath } = body;
557
-
558
- let runner;
559
- let filePath = resultFilePath || null;
560
-
561
- try {
562
- runner = this._getRunner(taskId, stepId);
563
- if (!filePath) filePath = runner.resultPath;
564
- } catch (err) {
565
- // 没有活跃 runner(磁盘恢复的任务),从编排器步骤元信息获取 resultPath
566
- if (this._orchestratorController) {
567
- const stepInfo = this._orchestratorController.getStepInfo(taskId, stepId);
568
- if (stepInfo && stepInfo.resultPath) {
569
- filePath = stepInfo.resultPath;
570
- }
571
- }
572
- if (!filePath) {
573
- return res.error(404, err.message);
574
- }
575
- }
576
-
577
- if (!filePath) {
578
- return res.error(400, 'resultFilePath 为空且实例无记录的 resultPath');
579
- }
580
-
581
- // 读取文件内容并解析执行状态
582
- let content = null;
583
- let success = false;
584
- try {
585
- content = await fs.readFile(filePath, 'utf-8');
586
- if (content.charCodeAt(0) === 0xFEFF) content = content.slice(1);
587
- const match = content.match(/##\s*执行状态\s*\n\s*(成功|失败)/i);
588
- success = match ? match[1] === '成功' : false;
589
- } catch {
590
- /* 文件可能不存在 */
591
- }
592
-
593
- res.json({
594
- status: 'ok',
595
- success,
596
- resultFilePath: filePath,
597
- content,
598
- });
599
- }
600
-
601
- /**
602
- * GET /api/flexrunner/steps?taskId=xxx[&stepId=yyy][&loop=N]
603
- * 列出任务目录下的步骤记录文件
604
- *
605
- * - 可选 stepId:只返回该步骤的文件
606
- * - 可选 loop:只返回该大循环轮次的文件(匹配文件名中的 _loop{N}_ 段)
607
- */
608
- async steps(req, res) {
609
- const { taskId } = req.query;
610
- const filterStepId = req.query.stepId != null && req.query.stepId !== '' ? String(req.query.stepId) : null;
611
- const filterLoop = req.query.loop != null && req.query.loop !== '' ? Number(req.query.loop) : null;
612
-
613
- if (!taskId) {
614
- return res.error(400, 'taskId 为必填项');
615
- }
616
-
617
- // 从已注册的实例中查找该 taskId 的所有 step
618
- const stepList = [];
619
- const seenStepKeys = new Set();
620
- for (const [key, runner] of this._runners) {
621
- if (runner.taskId === taskId) {
622
- stepList.push(this._runnerToJSON(runner));
623
- seenStepKeys.add(this._key(runner.taskId, runner.stepId));
624
- }
625
- }
626
-
627
- // 从 OrchestratorController 补充编排器管理的步骤(覆盖编排执行创建的实例)
628
- if (this._orchestratorController) {
629
- const orchSteps = this._orchestratorController.getTaskSteps(taskId);
630
- for (const s of orchSteps) {
631
- const k = this._key(taskId, s.stepId);
632
- if (!seenStepKeys.has(k)) {
633
- stepList.push(s);
634
- seenStepKeys.add(k);
635
- }
636
- }
637
- }
638
-
639
- // 扫描该 task 自己的目录下的 .md 文件
640
- // taskDir 直接就是 db/task/{taskId},文件直接放在该目录下(不在子目录中)
641
- let files = [];
642
- const taskDir = stepList[0]?.taskDir || null;
643
- if (taskDir) {
644
- try {
645
- const allFiles = await fs.readdir(taskDir);
646
- for (const f of allFiles) {
647
- if (!f.endsWith('.md')) continue;
648
- const fp = path.join(taskDir, f);
649
- const fstat = await fs.stat(fp);
650
- if (!fstat.isFile()) continue;
651
-
652
- // 从文件名中解析 stepId 和 loop
653
- // 文件命名格式: stepId{N}_loop{L}_result_{R}.md 或 stepId{N}_loop{L}_Summary_{R}.md
654
- const stepIdMatch = f.match(/^stepId(\d+)/);
655
- const loopMatch = f.match(/_loop(\d+)_/);
656
- const fileStepId = stepIdMatch ? stepIdMatch[1] : null;
657
- const fileLoop = loopMatch != null ? Number(loopMatch[1]) : null;
658
-
659
- // 按 stepId 筛选
660
- if (filterStepId && fileStepId !== filterStepId) continue;
661
- // 按 loop 筛选
662
- if (filterLoop != null && fileLoop !== filterLoop) continue;
663
-
664
- files.push({
665
- stepId: fileStepId || f,
666
- loop: fileLoop,
667
- filename: f,
668
- path: fp,
669
- size: fstat.size,
670
- });
671
- }
672
- } catch { /* 目录不存在 */ }
673
- }
674
-
675
- res.json({
676
- status: 'ok',
677
- taskId,
678
- steps: stepList,
679
- files,
680
- });
681
- }
682
-
683
- /**
684
- * GET /api/flexrunner/file?path=xxx
685
- * 读取指定文件的内容(用于查看 result/summary .md)
686
- */
687
- async file(req, res) {
688
- const filePath = req.query.path;
689
- if (!filePath) {
690
- return res.error(400, 'path 为必填项');
691
- }
692
-
693
- try {
694
- let content = await fs.readFile(filePath, 'utf-8');
695
- if (content.charCodeAt(0) === 0xFEFF) content = content.slice(1);
696
- res.json({
697
- status: 'ok',
698
- path: filePath,
699
- content,
700
- });
701
- } catch (err) {
702
- res.error(404, `文件读取失败: ${err.message}`);
703
- }
704
- }
705
-
706
- /**
707
- * GET /api/flexrunner/archive?taskId=xxx
708
- * 打包下载任务目录下的全部 .md 报告文件(ZIP)。
709
- *
710
- * - 扫描 taskDir 根目录(不递归,排除 source/ 克隆仓库),收集所有 .md 文件
711
- * - 检视报告在前、框架产物(stepId*_result/Summary)在后,同类自然排序
712
- * - ZIP 内以 {taskId}/ 目录前缀组织,便于解压归档
713
- * - 任务进行中调用时打包当前已生成的文件(实时快照)
714
- */
715
- async archive(req, res) {
716
- const { taskId } = req.query;
717
- if (!taskId) {
718
- return res.error(400, 'taskId 为必填项');
719
- }
720
-
721
- try {
722
- // ── 1. 解析 taskDir ──
723
- const taskDir = await this._resolveTaskDir(taskId);
724
- if (!taskDir) {
725
- return res.error(404, `未找到任务目录: taskId=${taskId}`);
726
- }
727
-
728
- // ── 2. 收集根目录 .md 文件(withFileTypes + isFile,天然排除子目录)──
729
- const entries = await fs.readdir(taskDir, { withFileTypes: true });
730
- const mdFiles = entries
731
- .filter(e => e.isFile() && e.name.endsWith('.md'))
732
- .map(e => e.name);
733
-
734
- if (mdFiles.length === 0) {
735
- return res.error(404, '暂无报告文件(任务尚未生成任何 .md 产物)');
736
- }
737
-
738
- // ── 3. 排序:检视报告在前,框架产物(stepId*)在后 ──
739
- const isFrameworkFile = (n) => /^stepId/i.test(n);
740
- mdFiles.sort((a, b) => {
741
- const fa = isFrameworkFile(a) ? 1 : 0;
742
- const fb = isFrameworkFile(b) ? 1 : 0;
743
- if (fa !== fb) return fa - fb;
744
- return a.localeCompare(b, 'zh-CN', { numeric: true });
745
- });
746
-
747
- // ── 4. 读取并打包(单文件读取失败跳过,不中断整体打包)──
748
- const files = [];
749
- for (const name of mdFiles) {
750
- const fp = path.join(taskDir, name);
751
- try {
752
- const [data, st] = await Promise.all([fs.readFile(fp), fs.stat(fp)]);
753
- files.push({ name: `${taskId}/${name}`, data, mtime: st.mtime });
754
- } catch (err) {
755
- console.warn(`[FlexRunnerController] archive 跳过读取失败文件: ${fp}, ${err.message}`);
756
- }
757
- }
758
- if (files.length === 0) {
759
- return res.error(404, '报告文件读取失败');
760
- }
761
-
762
- const zip = createZipBuffer(files);
763
-
764
- // ── 5. 二进制响应(Content-Disposition 双写,兼容 ASCII 回退与 UTF-8)──
765
- const raw = res.raw;
766
- raw.writeHead(200, {
767
- 'Content-Type': 'application/zip',
768
- 'Content-Length': zip.length,
769
- 'Content-Disposition': `attachment; filename="${taskId}.zip"; filename*=UTF-8''${encodeURIComponent(taskId)}.zip`
770
- });
771
- raw.end(zip);
772
- } catch (err) {
773
- if (!res.raw.headersSent) {
774
- return res.error(500, `打包失败: ${err.message}`);
775
- }
776
- }
777
- }
778
-
779
- /**
780
- * 解析任务工作目录(供 archive 打包等场景使用)。
781
- *
782
- * 查找顺序:
783
- * 1. 内存中活跃 FlexRunner 实例的 taskDir
784
- * 2. OrchestratorController 中编排步骤元信息的 taskDir(含重启后恢复的实例)
785
- * 3. 磁盘兜底:{rootDir}/task/{taskId}(taskId 白名单校验 + 路径边界检查,防目录穿越)
786
- *
787
- * @param {string} taskId
788
- * @returns {Promise<string|null>} taskDir 绝对路径;未找到返回 null
789
- * @private
790
- */
791
- async _resolveTaskDir(taskId) {
792
- // 1. 活跃 runner
793
- for (const runner of this._runners.values()) {
794
- if (runner.taskId === taskId && runner.taskDir) {
795
- return runner.taskDir;
796
- }
797
- }
798
-
799
- // 2. 编排器步骤元信息
800
- if (this._orchestratorController) {
801
- const steps = this._orchestratorController.getTaskSteps(taskId);
802
- const withDir = steps.find(s => s && s.taskDir);
803
- if (withDir) return withDir.taskDir;
804
- }
805
-
806
- // 3. 磁盘兜底(白名单 + 边界校验)
807
- const rootDir = this._storage?.manager?.rootDir;
808
- if (rootDir) {
809
- if (!/^[A-Za-z0-9._-]+$/.test(taskId) || taskId === '.' || taskId === '..') {
810
- return null;
811
- }
812
- const taskRoot = path.join(rootDir, 'task');
813
- const resolved = path.resolve(taskRoot, taskId);
814
- if (!resolved.startsWith(taskRoot + path.sep)) {
815
- return null;
816
- }
817
- try {
818
- const st = await fs.stat(resolved);
819
- if (st.isDirectory()) return resolved;
820
- } catch { /* 目录不存在 */ }
821
- }
822
-
823
- return null;
824
- }
825
-
826
- /**
827
- * GET /api/flexrunner/runners
828
- * 列出所有已注册的 FlexRunner 实例
829
- */
830
- async runners(req, res) {
831
- const list = [];
832
- for (const runner of this._runners.values()) {
833
- list.push(this._runnerToJSON(runner));
834
- }
835
- res.json({
836
- status: 'ok',
837
- count: list.length,
838
- runners: list,
839
- });
840
- }
841
-
842
- // ─── 单次执行 ─────────────────────────────────────────────────────
843
-
844
- /**
845
- * POST /api/flexrunner/start-single
846
- * 启动单次执行 — 创建一个只含单步骤的编排任务,不走 Brain 拆解和 LoopEngine 循环。
847
- *
848
- * 内部流程:
849
- * 1. 生成 taskId(如果未指定)
850
- * 2. 构造单步骤 taskDef(stepId=1, dependId=0, nextStepId=0, failStepId=0)
851
- * 3. 委托 OrchestratorController.load() 加载任务 → 写 orchestration.json
852
- * 4. 委托 OrchestratorController.start() 异步启动编排
853
- * 5. 写精简版 loop.json(isSingleExecution=true)使任务列表可见
854
- * 6. 编排结束后更新 loop.json 终态 + 释放重型引用
855
- *
856
- * Body: {
857
- * taskDescription: string, // 必填
858
- * skillDirectory?: string, // 可选
859
- * specifiedSkill?: string, // 可选,指定 SKILL(name 或目录名),跳过自动匹配
860
- * taskDir?: string, // 可选
861
- * inputReport?: string, // 可选
862
- * maxRetries?: number, // 可选,默认 3
863
- * taskId?: string // 可选
864
- * }
865
- *
866
- * 响应:异步启动,立即返回 taskId 和 stepId。
867
- */
868
- async startSingle(req, res) {
869
- const body = req.body || {};
870
- const { taskDescription } = body;
871
-
872
- if (!taskDescription) {
873
- return res.error(400, 'taskDescription 为必填项');
874
- }
875
- if (!this._orchestratorController) {
876
- return res.error(500, 'orchestratorController 未初始化');
877
- }
878
-
879
- try {
880
- // ── 1. 生成 taskId ──
881
- const taskId = body.taskId ||
882
- `single_${Date.now().toString(36)}${Math.random().toString(36).substring(2, 6)}`;
883
-
884
- // 幂等保护:同一 taskId 若已有编排在运行,直接返回
885
- let existingOrch;
886
- try {
887
- existingOrch = this._orchestratorController._getOrchestrator(taskId);
888
- } catch { /* 不存在,正常 */ }
889
- if (existingOrch && existingOrch.status === OrchestratorStatus.RUNNING) {
890
- return res.json({
891
- status: 'ok',
892
- message: `单次执行已在运行中: taskId=${taskId}`,
893
- taskId,
894
- stepId: 1,
895
- progress: existingOrch.getProgress()
896
- });
897
- }
898
-
899
- // 若已存在同 taskId 的旧编排(已结束),先终止并移除
900
- if (existingOrch) {
901
- try { await existingOrch.abort(); } catch { /* 已结束 */ }
902
- this._orchestratorController.disposeOrchestrator(taskId, { removeInstance: true });
903
- }
904
-
905
- // ── 2. 构造单步骤 taskDef ──
906
- const maxRetries = typeof body.maxRetries === 'number' && body.maxRetries > 0
907
- ? body.maxRetries
908
- : 3;
909
- const skillDirectory = body.skillDirectory || '';
910
-
911
- // taskDir 默认推导(Orchestrator.loadTask 内部也会推导,但 loop.json 需要提前知道)
912
- const rootDir = this._storage?.manager?.rootDir;
913
- const taskDir = body.taskDir ||
914
- (rootDir
915
- ? path.resolve(rootDir, 'task', taskId)
916
- : path.resolve(process.cwd(), 'db', 'task', taskId));
917
-
918
- // 确保 taskDir 存在(opencode session 需要工作目录存在)
919
- await fs.mkdir(taskDir, { recursive: true });
920
-
921
- const taskDef = {
922
- taskId,
923
- taskDir,
924
- skillDirectory,
925
- loopCount: 0, // 单次执行固定为第 0 轮
926
- steps: [{
927
- stepId: 1,
928
- taskDescription,
929
- dependId: 0, // 入口步骤
930
- nextStepId: 0, // 无后续
931
- failStepId: 0, // 失败即终止
932
- maxRetries,
933
- skillDirectory,
934
- specifiedSkill: body.specifiedSkill || '',
935
- taskDir,
936
- inputReport: body.inputReport || ''
937
- }]
938
- };
939
-
940
- // ── 3. 委托 OrchestratorController 加载任务 ──
941
- const loadReq = { body: taskDef };
942
- const loadRes = this._createMockResponse();
943
- await this._orchestratorController.load(loadReq, loadRes);
944
-
945
- if (loadRes._status >= 400) {
946
- return res.error(loadRes._status, loadRes._data?.error || loadRes._data?.message || '编排任务加载失败');
947
- }
948
-
949
- // ── 4. 启动编排(直接调用 orch.start(),便于跟踪完成) ──
950
- const orch = this._orchestratorController._getOrchestrator(taskId);
951
-
952
- // ── 5. 写精简版 loop.json(使任务列表可见) ──
953
- await this._writeSingleLoopJson(taskId, taskDescription, taskDir, skillDirectory, 'executing', null, null);
954
-
955
- // ── 6. 异步启动编排,完成后更新 loop.json 终态 + 释放重型引用 ──
956
- orch.start()
957
- .catch(err => {
958
- console.error(`[FlexRunnerController] 单次执行异常: taskId=${taskId}, ${err.message}`);
959
- })
960
- .finally(() => {
961
- this._onSingleExecutionComplete(taskId, orch).catch(err => {
962
- console.warn(`[FlexRunnerController] 单次执行收尾失败: taskId=${taskId}, ${err.message}`);
963
- });
964
- });
965
-
966
- res.json({
967
- status: 'ok',
968
- message: `单次执行已启动: taskId=${taskId}`,
969
- taskId,
970
- stepId: 1,
971
- progress: orch.getProgress()
972
- });
973
-
974
- } catch (err) {
975
- res.error(400, err.message);
976
- }
977
- }
978
-
979
- /**
980
- * 创建模拟 Response 对象(供调用 OrchestratorController 的 load/start)。
981
- * @private
982
- */
983
- _createMockResponse() {
984
- const mock = {
985
- _status: 200,
986
- _data: null,
987
- statusCode: 200,
988
- headersSent: false,
989
- writableEnded: false,
990
- json(data) {
991
- this._data = data;
992
- this.writableEnded = true;
993
- },
994
- error(code, message) {
995
- this._status = code;
996
- this._data = { status: 'error', error: message };
997
- this.writableEnded = true;
998
- }
999
- };
1000
- return mock;
1001
- }
1002
-
1003
- /**
1004
- * 写精简版 loop.json(单次执行任务专用)。
1005
- *
1006
- * 格式与 LoopEngine._persistLoopState 兼容,附加 isSingleExecution 标记。
1007
- *
1008
- * @param {string} taskId
1009
- * @param {string} taskDescription
1010
- * @param {string} taskDir
1011
- * @param {string} skillDirectory
1012
- * @param {string} status - executing | completed | failed | aborted
1013
- * @param {string|null} startTime
1014
- * @param {string|null} endTime
1015
- * @private
1016
- */
1017
- async _writeSingleLoopJson(taskId, taskDescription, taskDir, skillDirectory, status, startTime, endTime) {
1018
- if (!this._storage) return;
1019
- try {
1020
- // 映射 loop 状态到 orchestrator 状态(用于前端 rounds 渲染)
1021
- var orchStatus;
1022
- if (status === 'executing') orchStatus = 'running';
1023
- else if (status === 'passed') orchStatus = 'completed';
1024
- else if (status === 'aborted') orchStatus = 'aborted';
1025
- else orchStatus = 'failed';
1026
-
1027
- const state = {
1028
- taskId,
1029
- taskDescription,
1030
- userAcceptanceCriteria: '',
1031
- taskDir,
1032
- skillDirectory,
1033
- maxLoops: 1,
1034
- currentLoop: 1,
1035
- status,
1036
- startTime: startTime || nowCST(),
1037
- endTime,
1038
- error: null,
1039
- aborted: status === 'aborted',
1040
- isSingleExecution: true,
1041
- rounds: [{
1042
- loop: 0,
1043
- startTime: startTime || nowCST(),
1044
- endTime,
1045
- passed: status === 'passed',
1046
- error: null,
1047
- planningIsReplan: false,
1048
- planningStepCount: 1,
1049
- orchestratorStatus: orchStatus
1050
- }],
1051
- persistedAt: nowCST()
1052
- };
1053
- await this._storage.json('task').write(`${taskId}/loop`, state);
1054
- } catch (err) {
1055
- console.warn(`[FlexRunnerController] 写 loop.json 失败: taskId=${taskId}, ${err.message}`);
1056
- }
1057
- }
1058
-
1059
- /**
1060
- * 单次执行编排完成后的收尾处理。
1061
- *
1062
- * 1. 更新 loop.json 的终态状态
1063
- * 2. 释放 Orchestrator 重型引用(FlexRunner/CLI/LLM 等)
1064
- *
1065
- * @param {string} taskId
1066
- * @param {object} orch - Orchestrator 实例
1067
- * @private
1068
- */
1069
- async _onSingleExecutionComplete(taskId, orch) {
1070
- const status = orch.status;
1071
-
1072
- // 映射编排终态到 loop.json 状态
1073
- let loopStatus;
1074
- if (status === OrchestratorStatus.COMPLETED) {
1075
- loopStatus = 'passed';
1076
- } else if (status === OrchestratorStatus.ABORTED) {
1077
- loopStatus = 'aborted';
1078
- } else {
1079
- loopStatus = 'failed';
1080
- }
1081
-
1082
- const startTime = orch.startTime;
1083
- const endTime = orch.endTime;
1084
-
1085
- // 读取 taskDescription(从编排器获取)
1086
- const progress = orch.getProgress();
1087
- const step = progress.steps && progress.steps[0];
1088
- const taskDescription = step ? step.taskDescription : '';
1089
- const taskDir = orch._taskDir || '';
1090
- const skillDirectory = orch._skillDirectory || '';
1091
-
1092
- // 更新 loop.json
1093
- await this._writeSingleLoopJson(
1094
- taskId, taskDescription, taskDir, skillDirectory,
1095
- loopStatus, startTime, endTime
1096
- );
1097
-
1098
- // 释放重型引用
1099
- if (this._orchestratorController) {
1100
- try {
1101
- this._orchestratorController.disposeOrchestrator(taskId, { removeInstance: true });
1102
- } catch (err) {
1103
- console.warn(`[FlexRunnerController] 释放 Orchestrator 失败: taskId=${taskId}, ${err.message}`);
1104
- }
1105
- }
1106
- }
1107
-
1108
- /**
1109
- * DELETE /api/flexrunner/runner?taskId=xxx&stepId=yyy
1110
- * 移除一个已注册的 FlexRunner 实例
1111
- */
1112
- async removeRunner(req, res) {
1113
- const taskId = req.query.taskId;
1114
- const stepId = Number(req.query.stepId);
1115
- const key = this._key(taskId, stepId);
1116
- const existed = this._runners.delete(key);
1117
- res.json({
1118
- status: 'ok',
1119
- removed: existed,
1120
- message: existed ? '实例已移除' : '实例不存在',
1121
- });
1122
- }
1123
- }
1124
-
1125
- export default FlexRunnerController;
1
+ import { FlexRunner, ControlType } from '../../FlexRunner/FlexRunner.js';
2
+ import { OrchestratorStatus } from '../../Orchestrator/Orchestrator.js';
3
+ import { nowCST } from '../../library/cstTime.js';
4
+ import { createZipBuffer } from '../library/zipArchive.js';
5
+ import fs from 'node:fs/promises';
6
+ import path from 'node:path';
7
+
8
+ /**
9
+ * FlexRunnerController —— FlexRunner 执行控制器
10
+ *
11
+ * 职责:封装 FlexRunner 的 HTTP 接口,供前端测试和操作。
12
+ * 不感知 HTTP 层细节,只接收 RequestContext / ResponseContext。
13
+ *
14
+ * 管理多个 FlexRunner 实例(按 taskId + stepId 区分),支持:
15
+ * - 初始化任务
16
+ * - 执行单步(executeStart)
17
+ * - 查询状态
18
+ * - 控制执行(暂停/继续/终止)
19
+ * - 获取实时消息
20
+ * - 获取会话内容
21
+ * - 检查结果文件
22
+ * - 列出任务步骤记录
23
+ */
24
+ class FlexRunnerController {
25
+ /**
26
+ * @param {object} [deps] - 共享依赖
27
+ * @param {object} [deps.orchestratorController] - OrchestratorController 实例(用于从编排器中查找 FlexRunner)
28
+ * @param {object} [deps.storage] - Storage 实例(用于在没有活跃 runner 时直接读取 session 归档)
29
+ */
30
+ constructor(deps = {}) {
31
+ /** @type {Map<string, FlexRunner>} key = `${taskId}::${stepId}` */
32
+ this._runners = new Map();
33
+ /** @type {Map<string, {logs:string[], status:string}>} key = taskId */
34
+ this._taskLogs = new Map();
35
+ /** @type {object|null} OrchestratorController 实例 */
36
+ this._orchestratorController = deps.orchestratorController || null;
37
+ /** @type {object|null} Storage 实例 */
38
+ this._storage = deps.storage || null;
39
+ }
40
+
41
+ // ─── 辅助方法 ──────────────────────────────────────────────────────
42
+
43
+ /**
44
+ * 生成实例缓存 key。
45
+ */
46
+ _key(taskId, stepId) {
47
+ return `${taskId}::${stepId}`;
48
+ }
49
+
50
+ /**
51
+ * 获取已有实例,不存在则报错。
52
+ *
53
+ * 查找顺序:
54
+ * 1. 本地 _runners Map(通过 /init 注册的实例)
55
+ * 2. OrchestratorController 中编排器步骤节点的 _runner(编排执行时创建的实例)
56
+ */
57
+ _getRunner(taskId, stepId) {
58
+ // 1. 本地 Map
59
+ const key = this._key(taskId, stepId);
60
+ const localRunner = this._runners.get(key);
61
+ if (localRunner) return localRunner;
62
+
63
+ // 2. 从 OrchestratorController 的编排器实例中查找
64
+ if (this._orchestratorController) {
65
+ const runner = this._orchestratorController.getStepRunner(taskId, stepId);
66
+ if (runner) return runner;
67
+ }
68
+
69
+ throw new Error(`未找到任务实例: taskId=${taskId}, stepId=${stepId},请先初始化或执行编排`);
70
+ }
71
+
72
+ /**
73
+ * 在没有活跃 FlexRunner 实例时,从编排器步骤元信息中提取 sessionId,
74
+ * 然后直接读取本地 session 归档返回消息列表。
75
+ *
76
+ * 用于服务重启后(runner 不在内存中)仍能查看已完成步骤的对话记录。
77
+ *
78
+ * @param {string} taskId
79
+ * @param {number} stepId
80
+ * @param {number} [loop] - 大循环轮次(0-based)。指定时从磁盘 orchestration_loop{N}.json 读取历史轮次步骤信息
81
+ * @returns {Promise<{sessionId:string, messages:Array, meta:object|null}|null>}
82
+ */
83
+ async _readSessionArchiveFromStep(taskId, stepId, loop) {
84
+ if (!this._orchestratorController || !this._storage) return null;
85
+
86
+ // 指定轮次:从磁盘读取历史轮次的步骤信息
87
+ let stepInfo;
88
+ if (loop != null) {
89
+ stepInfo = await this._orchestratorController.getStepInfoByLoop(taskId, stepId, loop);
90
+ } else {
91
+ stepInfo = this._orchestratorController.getStepInfo(taskId, stepId);
92
+ }
93
+ if (!stepInfo || !stepInfo.sessionId) return null;
94
+
95
+ const sessionId = stepInfo.sessionId;
96
+ const sessionStore = this._storage.sessionStore();
97
+
98
+ const [meta, messages] = await Promise.all([
99
+ sessionStore.readMeta(sessionId).catch(() => null),
100
+ sessionStore.readMessages(sessionId).catch(() => [])
101
+ ]);
102
+
103
+ return { sessionId, messages, meta };
104
+ }
105
+
106
+ /**
107
+ * 将 FlexRunner 实例的状态序列化为 JSON。
108
+ */
109
+ _runnerToJSON(runner) {
110
+ return {
111
+ taskId: runner.taskId,
112
+ stepId: runner.stepId,
113
+ dependId: runner.dependId,
114
+ nextStepId: runner.nextStepId,
115
+ taskDescription: runner.taskDescription,
116
+ taskDir: runner.taskDir,
117
+ skillDirectory: runner.skillDirectory,
118
+ inputReport: runner.inputReport,
119
+ maxRetries: runner.maxRetries,
120
+ executeStep: runner.executeStep,
121
+ retryTimes: runner.retryTimes,
122
+ sessionId: runner.sessionId,
123
+ startTime: runner.startTime,
124
+ endTime: runner.endTime,
125
+ resultPath: runner.resultPath,
126
+ initialized: runner.initialized,
127
+ canRetry: runner.canRetry(),
128
+ };
129
+ }
130
+
131
+ // ─── HTTP Handlers ─────────────────────────────────────────────────
132
+
133
+ /**
134
+ * POST /api/flexrunner/init
135
+ * 初始化一个 FlexRunner 实例
136
+ *
137
+ * Body: {
138
+ * taskId, stepId, dependId, nextStepId, failStepId, taskDescription,
139
+ * skillDirectory, taskDir, inputReport, maxRetries
140
+ * }
141
+ */
142
+ async init(req, res) {
143
+ const body = req.body || {};
144
+ const { taskId } = body;
145
+
146
+ if (!taskId) {
147
+ return res.error(400, 'taskId 为必填项');
148
+ }
149
+
150
+ const stepId = Number(body.stepId);
151
+ if (!Number.isInteger(stepId) || stepId <= 0) {
152
+ return res.error(400, 'stepId 必须为正整数');
153
+ }
154
+
155
+ // 如果同 key 实例已存在,覆盖(重新初始化)
156
+ const runner = new FlexRunner();
157
+ await runner.initContext({
158
+ taskId,
159
+ stepId,
160
+ dependId: Number(body.dependId) || 0,
161
+ nextStepId: Number(body.nextStepId) || 0,
162
+ failStepId: Number(body.failStepId) || 0,
163
+ taskDescription: body.taskDescription || '',
164
+ skillDirectory: body.skillDirectory || '',
165
+ taskDir: body.taskDir || '',
166
+ inputReport: body.inputReport || '',
167
+ maxRetries: typeof body.maxRetries === 'number' ? body.maxRetries : undefined,
168
+ });
169
+
170
+ const key = this._key(taskId, stepId);
171
+ this._runners.set(key, runner);
172
+
173
+ res.json({
174
+ status: 'ok',
175
+ message: `FlexRunner 已初始化: taskId=${taskId}, stepId=${stepId}`,
176
+ runner: this._runnerToJSON(runner),
177
+ });
178
+ }
179
+
180
+ /**
181
+ * POST /api/flexrunner/execute
182
+ * 执行单次 executeStart(不内部循环,由调用方决定是否重试)
183
+ *
184
+ * Body: { taskId, stepId }
185
+ */
186
+ async execute(req, res) {
187
+ const body = req.body || {};
188
+ const taskId = body.taskId;
189
+ const stepId = Number(body.stepId);
190
+
191
+ let runner;
192
+ try {
193
+ runner = this._getRunner(taskId, stepId);
194
+ } catch (err) {
195
+ return res.error(404, err.message);
196
+ }
197
+
198
+ if (!runner.canRetry()) {
199
+ return res.error(409, `已达最大重试次数或已终止,无法继续执行`);
200
+ }
201
+
202
+ const result = await runner.executeStart();
203
+
204
+ res.json({
205
+ status: 'ok',
206
+ result,
207
+ runner: this._runnerToJSON(runner),
208
+ });
209
+ }
210
+
211
+ /**
212
+ * POST /api/flexrunner/execute-loop
213
+ * 自动循环执行直到成功或不可重试
214
+ *
215
+ * Body: { taskId, stepId }
216
+ */
217
+ async executeLoop(req, res) {
218
+ const body = req.body || {};
219
+ const taskId = body.taskId;
220
+ const stepId = Number(body.stepId);
221
+
222
+ let runner;
223
+ try {
224
+ runner = this._getRunner(taskId, stepId);
225
+ } catch (err) {
226
+ return res.error(404, err.message);
227
+ }
228
+
229
+ const results = [];
230
+ let lastResult;
231
+
232
+ while (runner.canRetry()) {
233
+ lastResult = await runner.executeStart();
234
+ results.push({
235
+ attempt: runner.executeStep,
236
+ success: lastResult.success,
237
+ nextStepId: lastResult.nextStepId,
238
+ resultPath: lastResult.resultPath,
239
+ });
240
+
241
+ if (lastResult.success) break;
242
+ }
243
+
244
+ res.json({
245
+ status: lastResult?.success ? 'ok' : 'error',
246
+ totalAttempts: results.length,
247
+ results,
248
+ finalResult: lastResult,
249
+ runner: this._runnerToJSON(runner),
250
+ });
251
+ }
252
+
253
+ /**
254
+ * GET /api/flexrunner/status?taskId=xxx&stepId=yyy
255
+ * 查询 FlexRunner 实例状态
256
+ *
257
+ * 如果本地无实例,尝试从编排器恢复步骤状态后返回。
258
+ */
259
+ async status(req, res) {
260
+ const taskId = req.query.taskId;
261
+ const stepId = Number(req.query.stepId);
262
+
263
+ let runner;
264
+ try {
265
+ runner = this._getRunner(taskId, stepId);
266
+ res.json({
267
+ status: 'ok',
268
+ runner: this._runnerToJSON(runner),
269
+ });
270
+ } catch (err) {
271
+ // 编排器中存在该任务但步骤已结束(runner 未存活),返回步骤元信息
272
+ if (this._orchestratorController) {
273
+ const stepInfo = this._orchestratorController.getStepInfo(taskId, stepId);
274
+ if (stepInfo) {
275
+ return res.json({
276
+ status: 'ok',
277
+ runner: { ...stepInfo, initialized: false },
278
+ });
279
+ }
280
+ }
281
+ return res.error(404, err.message);
282
+ }
283
+ }
284
+
285
+ /**
286
+ * GET /api/flexrunner/live-messages?taskId=xxx&stepId=yyy[&loop=N]
287
+ * 获取当前会话的实时对话消息
288
+ *
289
+ * - 不传 loop:从内存编排器实例查找(最新轮次)
290
+ * - 传 loop=N:从磁盘 orchestration_loop{N}.json 读取历史轮次步骤的 sessionId,再读取本地归档
291
+ */
292
+ async liveMessages(req, res) {
293
+ const taskId = req.query.taskId;
294
+ const stepId = Number(req.query.stepId);
295
+ const loop = req.query.loop != null && req.query.loop !== '' ? Number(req.query.loop) : undefined;
296
+
297
+ // 指定历史轮次:直接从磁盘读取该轮次的步骤归档(不查内存最新实例)
298
+ if (loop != null) {
299
+ const archive = await this._readSessionArchiveFromStep(taskId, stepId, loop);
300
+ if (archive) {
301
+ return res.json({
302
+ status: 'ok',
303
+ sessionId: archive.sessionId,
304
+ count: archive.messages.length,
305
+ messages: archive.messages,
306
+ });
307
+ }
308
+ return res.error(404, `未找到第 ${loop} 轮步骤归档: taskId=${taskId}, stepId=${stepId}`);
309
+ }
310
+
311
+ let runner;
312
+ try {
313
+ runner = this._getRunner(taskId, stepId);
314
+ } catch (err) {
315
+ // 没有活跃 runner(如服务重启后从磁盘恢复的任务),
316
+ // 尝试从编排器步骤元信息中获取 sessionId 并读取本地归档
317
+ const archive = await this._readSessionArchiveFromStep(taskId, stepId);
318
+ if (archive) {
319
+ return res.json({
320
+ status: 'ok',
321
+ sessionId: archive.sessionId,
322
+ count: archive.messages.length,
323
+ messages: archive.messages,
324
+ });
325
+ }
326
+ return res.error(404, err.message);
327
+ }
328
+
329
+ // 有活跃 runner,优先从远端拉取实时消息
330
+ try {
331
+ const messages = await runner.getLiveMessages();
332
+ return res.json({
333
+ status: 'ok',
334
+ sessionId: runner.sessionId,
335
+ count: messages.length,
336
+ messages,
337
+ });
338
+ } catch (liveErr) {
339
+ // 远端不可用(opencode 服务关闭/session 过期),回退到本地归档
340
+ const sessionId = runner.sessionId;
341
+ if (sessionId && this._storage) {
342
+ const sessionStore = this._storage.sessionStore();
343
+ const messages = await sessionStore.readMessages(sessionId).catch(() => []);
344
+ return res.json({
345
+ status: 'ok',
346
+ sessionId,
347
+ count: messages.length,
348
+ messages,
349
+ warning: '远端不可用,已回退到本地归档',
350
+ });
351
+ }
352
+ return res.error(500, `获取实时消息失败: ${liveErr.message}`);
353
+ }
354
+ }
355
+
356
+ /**
357
+ * POST /api/flexrunner/inject-prompt
358
+ * 向当前会话注入提示词/上下文(不触发 AI 回复)
359
+ *
360
+ * Body: { taskId, stepId, message, [loop] }
361
+ */
362
+ async injectPrompt(req, res) {
363
+ const body = req.body || {};
364
+ const taskId = body.taskId;
365
+ const stepId = Number(body.stepId);
366
+ const { message } = body;
367
+ const loop = body.loop != null ? Number(body.loop) : undefined;
368
+
369
+ if (!message || typeof message !== 'string') {
370
+ return res.error(400, 'message 为必填项且必须为字符串');
371
+ }
372
+
373
+ // 指定历史轮次:历史会话已结束,无法注入(仅支持查看归档)
374
+ if (loop != null) {
375
+ return res.error(409, `历史轮次(${loop})的会话已结束,不支持注入提示词`);
376
+ }
377
+
378
+ let runner;
379
+ try {
380
+ runner = this._getRunner(taskId, stepId);
381
+ } catch (err) {
382
+ return res.error(404, err.message);
383
+ }
384
+
385
+ const result = await runner.injectPrompt(message);
386
+
387
+ res.json({
388
+ status: result.success ? 'ok' : 'error',
389
+ sessionId: runner.sessionId,
390
+ success: result.success,
391
+ message: result.success
392
+ ? '提示词已注入'
393
+ : '注入失败(可能无活跃会话或 CLI 不支持)',
394
+ });
395
+ }
396
+
397
+ /**
398
+ * POST /api/flexrunner/send-and-wait
399
+ * 向当前会话发送消息并同步等待 AI 回复完成
400
+ *
401
+ * Body: { taskId, stepId, message, [loop] }
402
+ */
403
+ async sendAndWaitMessage(req, res) {
404
+ const body = req.body || {};
405
+ const taskId = body.taskId;
406
+ const stepId = Number(body.stepId);
407
+ const { message } = body;
408
+ const loop = body.loop != null ? Number(body.loop) : undefined;
409
+
410
+ if (!message || typeof message !== 'string') {
411
+ return res.error(400, 'message 为必填项且必须为字符串');
412
+ }
413
+
414
+ // 指定历史轮次:历史会话已结束,无法发送消息
415
+ if (loop != null) {
416
+ return res.error(409, `历史轮次(${loop})的会话已结束,不支持发送消息`);
417
+ }
418
+
419
+ let runner;
420
+ try {
421
+ runner = this._getRunner(taskId, stepId);
422
+ } catch (err) {
423
+ return res.error(404, err.message);
424
+ }
425
+
426
+ const result = await runner.sendAndWaitMessage(message);
427
+
428
+ res.json({
429
+ status: result.success ? 'ok' : 'error',
430
+ sessionId: runner.sessionId,
431
+ success: result.success,
432
+ response: result.response,
433
+ content: result.content,
434
+ message: result.success
435
+ ? '已收到 AI 回复'
436
+ : '发送失败(可能无活跃会话或 CLI 不支持)',
437
+ });
438
+ }
439
+
440
+ /**
441
+ * GET /api/flexrunner/session?taskId=xxx&stepId=yyy[&loop=N]
442
+ * 获取当前会话的完整归档内容
443
+ *
444
+ * - 不传 loop:从内存编排器实例查找(最新轮次)
445
+ * - 传 loop=N:从磁盘 orchestration_loop{N}.json 读取历史轮次步骤的 sessionId,再读取本地归档
446
+ */
447
+ async session(req, res) {
448
+ const taskId = req.query.taskId;
449
+ const stepId = Number(req.query.stepId);
450
+ const loop = req.query.loop != null && req.query.loop !== '' ? Number(req.query.loop) : undefined;
451
+
452
+ // 指定历史轮次:直接从磁盘读取该轮次的步骤归档
453
+ if (loop != null) {
454
+ const archive = await this._readSessionArchiveFromStep(taskId, stepId, loop);
455
+ if (archive) {
456
+ return res.json({
457
+ status: 'ok',
458
+ sessionId: archive.sessionId,
459
+ meta: archive.meta,
460
+ messages: archive.messages,
461
+ });
462
+ }
463
+ return res.error(404, `未找到第 ${loop} 轮步骤归档: taskId=${taskId}, stepId=${stepId}`);
464
+ }
465
+
466
+ let runner;
467
+ try {
468
+ runner = this._getRunner(taskId, stepId);
469
+ } catch (err) {
470
+ // 没有活跃 runner,从编排器步骤元信息获取 sessionId 并读取本地归档
471
+ const archive = await this._readSessionArchiveFromStep(taskId, stepId);
472
+ if (archive) {
473
+ return res.json({
474
+ status: 'ok',
475
+ sessionId: archive.sessionId,
476
+ meta: archive.meta,
477
+ messages: archive.messages,
478
+ });
479
+ }
480
+ return res.error(404, err.message);
481
+ }
482
+
483
+ try {
484
+ const jsonStr = await runner.getSessionContent();
485
+ const data = JSON.parse(jsonStr);
486
+
487
+ res.json({
488
+ status: 'ok',
489
+ sessionId: runner.sessionId,
490
+ ...data,
491
+ });
492
+ } catch (err) {
493
+ // runner 存在但读取失败,尝试直接从本地归档恢复
494
+ const sessionId = runner.sessionId;
495
+ if (sessionId && this._storage) {
496
+ const sessionStore = this._storage.sessionStore();
497
+ const [meta, messages] = await Promise.all([
498
+ sessionStore.readMeta(sessionId).catch(() => null),
499
+ sessionStore.readMessages(sessionId).catch(() => [])
500
+ ]);
501
+ return res.json({
502
+ status: 'ok',
503
+ sessionId,
504
+ meta,
505
+ messages,
506
+ warning: '已回退到本地归档',
507
+ });
508
+ }
509
+ return res.error(500, `获取会话内容失败: ${err.message}`);
510
+ }
511
+ }
512
+
513
+ /**
514
+ * POST /api/flexrunner/control
515
+ * 控制执行(暂停/继续/终止)
516
+ *
517
+ * Body: { taskId, stepId, type: 'pause'|'resume'|'abort' }
518
+ */
519
+ async control(req, res) {
520
+ const body = req.body || {};
521
+ const taskId = body.taskId;
522
+ const stepId = Number(body.stepId);
523
+ const { type } = body;
524
+
525
+ let runner;
526
+ try {
527
+ runner = this._getRunner(taskId, stepId);
528
+ } catch (err) {
529
+ return res.error(404, err.message);
530
+ }
531
+
532
+ const validTypes = ['pause', 'resume', 'abort'];
533
+ if (!validTypes.includes(type)) {
534
+ return res.error(400, `type 必须为: ${validTypes.join(', ')}`);
535
+ }
536
+
537
+ const ok = await runner.controlExecution(type);
538
+
539
+ res.json({
540
+ status: ok ? 'ok' : 'error',
541
+ message: `控制指令 "${type}" 已${ok ? '执行' : '失败'}`,
542
+ runner: this._runnerToJSON(runner),
543
+ });
544
+ }
545
+
546
+ /**
547
+ * POST /api/flexrunner/check-result
548
+ * 检查结果文件的成功/失败状态
549
+ *
550
+ * Body: { taskId, stepId, resultFilePath }
551
+ */
552
+ async checkResult(req, res) {
553
+ const body = req.body || {};
554
+ const taskId = body.taskId;
555
+ const stepId = Number(body.stepId);
556
+ const { resultFilePath } = body;
557
+
558
+ let runner;
559
+ let filePath = resultFilePath || null;
560
+
561
+ try {
562
+ runner = this._getRunner(taskId, stepId);
563
+ if (!filePath) filePath = runner.resultPath;
564
+ } catch (err) {
565
+ // 没有活跃 runner(磁盘恢复的任务),从编排器步骤元信息获取 resultPath
566
+ if (this._orchestratorController) {
567
+ const stepInfo = this._orchestratorController.getStepInfo(taskId, stepId);
568
+ if (stepInfo && stepInfo.resultPath) {
569
+ filePath = stepInfo.resultPath;
570
+ }
571
+ }
572
+ if (!filePath) {
573
+ return res.error(404, err.message);
574
+ }
575
+ }
576
+
577
+ if (!filePath) {
578
+ return res.error(400, 'resultFilePath 为空且实例无记录的 resultPath');
579
+ }
580
+
581
+ // 读取文件内容并解析执行状态
582
+ let content = null;
583
+ let success = false;
584
+ try {
585
+ content = await fs.readFile(filePath, 'utf-8');
586
+ if (content.charCodeAt(0) === 0xFEFF) content = content.slice(1);
587
+ const match = content.match(/##\s*执行状态\s*\n\s*(成功|失败)/i);
588
+ success = match ? match[1] === '成功' : false;
589
+ } catch {
590
+ /* 文件可能不存在 */
591
+ }
592
+
593
+ res.json({
594
+ status: 'ok',
595
+ success,
596
+ resultFilePath: filePath,
597
+ content,
598
+ });
599
+ }
600
+
601
+ /**
602
+ * GET /api/flexrunner/steps?taskId=xxx[&stepId=yyy][&loop=N]
603
+ * 列出任务目录下的步骤记录文件
604
+ *
605
+ * - 可选 stepId:只返回该步骤的文件
606
+ * - 可选 loop:只返回该大循环轮次的文件(匹配文件名中的 _loop{N}_ 段)
607
+ */
608
+ async steps(req, res) {
609
+ const { taskId } = req.query;
610
+ const filterStepId = req.query.stepId != null && req.query.stepId !== '' ? String(req.query.stepId) : null;
611
+ const filterLoop = req.query.loop != null && req.query.loop !== '' ? Number(req.query.loop) : null;
612
+
613
+ if (!taskId) {
614
+ return res.error(400, 'taskId 为必填项');
615
+ }
616
+
617
+ // 从已注册的实例中查找该 taskId 的所有 step
618
+ const stepList = [];
619
+ const seenStepKeys = new Set();
620
+ for (const [key, runner] of this._runners) {
621
+ if (runner.taskId === taskId) {
622
+ stepList.push(this._runnerToJSON(runner));
623
+ seenStepKeys.add(this._key(runner.taskId, runner.stepId));
624
+ }
625
+ }
626
+
627
+ // 从 OrchestratorController 补充编排器管理的步骤(覆盖编排执行创建的实例)
628
+ if (this._orchestratorController) {
629
+ const orchSteps = this._orchestratorController.getTaskSteps(taskId);
630
+ for (const s of orchSteps) {
631
+ const k = this._key(taskId, s.stepId);
632
+ if (!seenStepKeys.has(k)) {
633
+ stepList.push(s);
634
+ seenStepKeys.add(k);
635
+ }
636
+ }
637
+ }
638
+
639
+ // 扫描该 task 自己的目录下的 .md 文件
640
+ // taskDir 直接就是 db/task/{taskId},文件直接放在该目录下(不在子目录中)
641
+ let files = [];
642
+ const taskDir = stepList[0]?.taskDir || null;
643
+ if (taskDir) {
644
+ try {
645
+ const allFiles = await fs.readdir(taskDir);
646
+ for (const f of allFiles) {
647
+ if (!f.endsWith('.md')) continue;
648
+ const fp = path.join(taskDir, f);
649
+ const fstat = await fs.stat(fp);
650
+ if (!fstat.isFile()) continue;
651
+
652
+ // 从文件名中解析 stepId 和 loop
653
+ // 文件命名格式: stepId{N}_loop{L}_result_{R}.md 或 stepId{N}_loop{L}_Summary_{R}.md
654
+ const stepIdMatch = f.match(/^stepId(\d+)/);
655
+ const loopMatch = f.match(/_loop(\d+)_/);
656
+ const fileStepId = stepIdMatch ? stepIdMatch[1] : null;
657
+ const fileLoop = loopMatch != null ? Number(loopMatch[1]) : null;
658
+
659
+ // 按 stepId 筛选
660
+ if (filterStepId && fileStepId !== filterStepId) continue;
661
+ // 按 loop 筛选
662
+ if (filterLoop != null && fileLoop !== filterLoop) continue;
663
+
664
+ files.push({
665
+ stepId: fileStepId || f,
666
+ loop: fileLoop,
667
+ filename: f,
668
+ path: fp,
669
+ size: fstat.size,
670
+ });
671
+ }
672
+ } catch { /* 目录不存在 */ }
673
+ }
674
+
675
+ res.json({
676
+ status: 'ok',
677
+ taskId,
678
+ steps: stepList,
679
+ files,
680
+ });
681
+ }
682
+
683
+ /**
684
+ * GET /api/flexrunner/file?path=xxx
685
+ * 读取指定文件的内容(用于查看 result/summary .md)
686
+ */
687
+ async file(req, res) {
688
+ const filePath = req.query.path;
689
+ if (!filePath) {
690
+ return res.error(400, 'path 为必填项');
691
+ }
692
+
693
+ try {
694
+ let content = await fs.readFile(filePath, 'utf-8');
695
+ if (content.charCodeAt(0) === 0xFEFF) content = content.slice(1);
696
+ res.json({
697
+ status: 'ok',
698
+ path: filePath,
699
+ content,
700
+ });
701
+ } catch (err) {
702
+ res.error(404, `文件读取失败: ${err.message}`);
703
+ }
704
+ }
705
+
706
+ /**
707
+ * GET /api/flexrunner/archive?taskId=xxx
708
+ * 打包下载任务目录下的全部 .md 报告文件(ZIP)。
709
+ *
710
+ * - 扫描 taskDir 根目录(不递归,排除 source/ 克隆仓库),收集所有 .md 文件
711
+ * - 检视报告在前、框架产物(stepId*_result/Summary)在后,同类自然排序
712
+ * - ZIP 内以 {taskId}/ 目录前缀组织,便于解压归档
713
+ * - 任务进行中调用时打包当前已生成的文件(实时快照)
714
+ */
715
+ async archive(req, res) {
716
+ const { taskId } = req.query;
717
+ if (!taskId) {
718
+ return res.error(400, 'taskId 为必填项');
719
+ }
720
+
721
+ try {
722
+ // ── 1. 解析 taskDir ──
723
+ const taskDir = await this._resolveTaskDir(taskId);
724
+ if (!taskDir) {
725
+ return res.error(404, `未找到任务目录: taskId=${taskId}`);
726
+ }
727
+
728
+ // ── 2. 收集根目录 .md 文件(withFileTypes + isFile,天然排除子目录)──
729
+ const entries = await fs.readdir(taskDir, { withFileTypes: true });
730
+ const mdFiles = entries
731
+ .filter(e => e.isFile() && e.name.endsWith('.md'))
732
+ .map(e => e.name);
733
+
734
+ if (mdFiles.length === 0) {
735
+ return res.error(404, '暂无报告文件(任务尚未生成任何 .md 产物)');
736
+ }
737
+
738
+ // ── 3. 排序:检视报告在前,框架产物(stepId*)在后 ──
739
+ const isFrameworkFile = (n) => /^stepId/i.test(n);
740
+ mdFiles.sort((a, b) => {
741
+ const fa = isFrameworkFile(a) ? 1 : 0;
742
+ const fb = isFrameworkFile(b) ? 1 : 0;
743
+ if (fa !== fb) return fa - fb;
744
+ return a.localeCompare(b, 'zh-CN', { numeric: true });
745
+ });
746
+
747
+ // ── 4. 读取并打包(单文件读取失败跳过,不中断整体打包)──
748
+ const files = [];
749
+ for (const name of mdFiles) {
750
+ const fp = path.join(taskDir, name);
751
+ try {
752
+ const [data, st] = await Promise.all([fs.readFile(fp), fs.stat(fp)]);
753
+ files.push({ name: `${taskId}/${name}`, data, mtime: st.mtime });
754
+ } catch (err) {
755
+ console.warn(`[FlexRunnerController] archive 跳过读取失败文件: ${fp}, ${err.message}`);
756
+ }
757
+ }
758
+ if (files.length === 0) {
759
+ return res.error(404, '报告文件读取失败');
760
+ }
761
+
762
+ const zip = createZipBuffer(files);
763
+
764
+ // ── 5. 二进制响应(Content-Disposition 双写,兼容 ASCII 回退与 UTF-8)──
765
+ const raw = res.raw;
766
+ raw.writeHead(200, {
767
+ 'Content-Type': 'application/zip',
768
+ 'Content-Length': zip.length,
769
+ 'Content-Disposition': `attachment; filename="${taskId}.zip"; filename*=UTF-8''${encodeURIComponent(taskId)}.zip`
770
+ });
771
+ raw.end(zip);
772
+ } catch (err) {
773
+ if (!res.raw.headersSent) {
774
+ return res.error(500, `打包失败: ${err.message}`);
775
+ }
776
+ }
777
+ }
778
+
779
+ /**
780
+ * 解析任务工作目录(供 archive 打包等场景使用)。
781
+ *
782
+ * 查找顺序:
783
+ * 1. 内存中活跃 FlexRunner 实例的 taskDir
784
+ * 2. OrchestratorController 中编排步骤元信息的 taskDir(含重启后恢复的实例)
785
+ * 3. 磁盘兜底:{rootDir}/task/{taskId}(taskId 白名单校验 + 路径边界检查,防目录穿越)
786
+ *
787
+ * @param {string} taskId
788
+ * @returns {Promise<string|null>} taskDir 绝对路径;未找到返回 null
789
+ * @private
790
+ */
791
+ async _resolveTaskDir(taskId) {
792
+ // 1. 活跃 runner
793
+ for (const runner of this._runners.values()) {
794
+ if (runner.taskId === taskId && runner.taskDir) {
795
+ return runner.taskDir;
796
+ }
797
+ }
798
+
799
+ // 2. 编排器步骤元信息
800
+ if (this._orchestratorController) {
801
+ const steps = this._orchestratorController.getTaskSteps(taskId);
802
+ const withDir = steps.find(s => s && s.taskDir);
803
+ if (withDir) return withDir.taskDir;
804
+ }
805
+
806
+ // 3. 磁盘兜底(白名单 + 边界校验)
807
+ const rootDir = this._storage?.manager?.rootDir;
808
+ if (rootDir) {
809
+ if (!/^[A-Za-z0-9._-]+$/.test(taskId) || taskId === '.' || taskId === '..') {
810
+ return null;
811
+ }
812
+ const taskRoot = path.join(rootDir, 'task');
813
+ const resolved = path.resolve(taskRoot, taskId);
814
+ if (!resolved.startsWith(taskRoot + path.sep)) {
815
+ return null;
816
+ }
817
+ try {
818
+ const st = await fs.stat(resolved);
819
+ if (st.isDirectory()) return resolved;
820
+ } catch { /* 目录不存在 */ }
821
+ }
822
+
823
+ return null;
824
+ }
825
+
826
+ /**
827
+ * GET /api/flexrunner/runners
828
+ * 列出所有已注册的 FlexRunner 实例
829
+ */
830
+ async runners(req, res) {
831
+ const list = [];
832
+ for (const runner of this._runners.values()) {
833
+ list.push(this._runnerToJSON(runner));
834
+ }
835
+ res.json({
836
+ status: 'ok',
837
+ count: list.length,
838
+ runners: list,
839
+ });
840
+ }
841
+
842
+ // ─── 单次执行 ─────────────────────────────────────────────────────
843
+
844
+ /**
845
+ * POST /api/flexrunner/start-single
846
+ * 启动单次执行 — 创建一个只含单步骤的编排任务,不走 Brain 拆解和 LoopEngine 循环。
847
+ *
848
+ * 内部流程:
849
+ * 1. 生成 taskId(如果未指定)
850
+ * 2. 构造单步骤 taskDef(stepId=1, dependId=0, nextStepId=0, failStepId=0)
851
+ * 3. 委托 OrchestratorController.load() 加载任务 → 写 orchestration.json
852
+ * 4. 委托 OrchestratorController.start() 异步启动编排
853
+ * 5. 写精简版 loop.json(isSingleExecution=true)使任务列表可见
854
+ * 6. 编排结束后更新 loop.json 终态 + 释放重型引用
855
+ *
856
+ * Body: {
857
+ * taskDescription: string, // 必填
858
+ * skillDirectory?: string, // 可选
859
+ * specifiedSkill?: string, // 可选,指定 SKILL(name 或目录名),跳过自动匹配
860
+ * taskDir?: string, // 可选
861
+ * inputReport?: string, // 可选
862
+ * maxRetries?: number, // 可选,默认 3
863
+ * taskId?: string // 可选
864
+ * }
865
+ *
866
+ * 响应:异步启动,立即返回 taskId 和 stepId。
867
+ */
868
+ async startSingle(req, res) {
869
+ const body = req.body || {};
870
+ const { taskDescription } = body;
871
+
872
+ if (!taskDescription) {
873
+ return res.error(400, 'taskDescription 为必填项');
874
+ }
875
+ if (!this._orchestratorController) {
876
+ return res.error(500, 'orchestratorController 未初始化');
877
+ }
878
+
879
+ try {
880
+ // ── 1. 生成 taskId ──
881
+ const taskId = body.taskId ||
882
+ `single_${Date.now().toString(36)}${Math.random().toString(36).substring(2, 6)}`;
883
+
884
+ // 幂等保护:同一 taskId 若已有编排在运行,直接返回
885
+ let existingOrch;
886
+ try {
887
+ existingOrch = this._orchestratorController._getOrchestrator(taskId);
888
+ } catch { /* 不存在,正常 */ }
889
+ if (existingOrch && existingOrch.status === OrchestratorStatus.RUNNING) {
890
+ return res.json({
891
+ status: 'ok',
892
+ message: `单次执行已在运行中: taskId=${taskId}`,
893
+ taskId,
894
+ stepId: 1,
895
+ progress: existingOrch.getProgress()
896
+ });
897
+ }
898
+
899
+ // 若已存在同 taskId 的旧编排(已结束),先终止并移除
900
+ if (existingOrch) {
901
+ try { await existingOrch.abort(); } catch { /* 已结束 */ }
902
+ this._orchestratorController.disposeOrchestrator(taskId, { removeInstance: true });
903
+ }
904
+
905
+ // ── 2. 构造单步骤 taskDef ──
906
+ const maxRetries = typeof body.maxRetries === 'number' && body.maxRetries > 0
907
+ ? body.maxRetries
908
+ : 3;
909
+ const skillDirectory = body.skillDirectory || '';
910
+
911
+ // taskDir 默认推导(Orchestrator.loadTask 内部也会推导,但 loop.json 需要提前知道)
912
+ const rootDir = this._storage?.manager?.rootDir;
913
+ const taskDir = body.taskDir ||
914
+ (rootDir
915
+ ? path.resolve(rootDir, 'task', taskId)
916
+ : path.resolve(process.cwd(), 'db', 'task', taskId));
917
+
918
+ // 确保 taskDir 存在(opencode session 需要工作目录存在)
919
+ await fs.mkdir(taskDir, { recursive: true });
920
+
921
+ const taskDef = {
922
+ taskId,
923
+ taskDir,
924
+ skillDirectory,
925
+ loopCount: 0, // 单次执行固定为第 0 轮
926
+ steps: [{
927
+ stepId: 1,
928
+ taskDescription,
929
+ dependId: 0, // 入口步骤
930
+ nextStepId: 0, // 无后续
931
+ failStepId: 0, // 失败即终止
932
+ maxRetries,
933
+ skillDirectory,
934
+ specifiedSkill: body.specifiedSkill || '',
935
+ taskDir,
936
+ inputReport: body.inputReport || ''
937
+ }]
938
+ };
939
+
940
+ // ── 3. 委托 OrchestratorController 加载任务 ──
941
+ const loadReq = { body: taskDef };
942
+ const loadRes = this._createMockResponse();
943
+ await this._orchestratorController.load(loadReq, loadRes);
944
+
945
+ if (loadRes._status >= 400) {
946
+ return res.error(loadRes._status, loadRes._data?.error || loadRes._data?.message || '编排任务加载失败');
947
+ }
948
+
949
+ // ── 4. 启动编排(直接调用 orch.start(),便于跟踪完成) ──
950
+ const orch = this._orchestratorController._getOrchestrator(taskId);
951
+
952
+ // ── 5. 写精简版 loop.json(使任务列表可见) ──
953
+ await this._writeSingleLoopJson(taskId, taskDescription, taskDir, skillDirectory, 'executing', null, null);
954
+
955
+ // ── 6. 异步启动编排,完成后更新 loop.json 终态 + 释放重型引用 ──
956
+ orch.start()
957
+ .catch(err => {
958
+ console.error(`[FlexRunnerController] 单次执行异常: taskId=${taskId}, ${err.message}`);
959
+ })
960
+ .finally(() => {
961
+ this._onSingleExecutionComplete(taskId, orch).catch(err => {
962
+ console.warn(`[FlexRunnerController] 单次执行收尾失败: taskId=${taskId}, ${err.message}`);
963
+ });
964
+ });
965
+
966
+ res.json({
967
+ status: 'ok',
968
+ message: `单次执行已启动: taskId=${taskId}`,
969
+ taskId,
970
+ stepId: 1,
971
+ progress: orch.getProgress()
972
+ });
973
+
974
+ } catch (err) {
975
+ res.error(400, err.message);
976
+ }
977
+ }
978
+
979
+ /**
980
+ * 创建模拟 Response 对象(供调用 OrchestratorController 的 load/start)。
981
+ * @private
982
+ */
983
+ _createMockResponse() {
984
+ const mock = {
985
+ _status: 200,
986
+ _data: null,
987
+ statusCode: 200,
988
+ headersSent: false,
989
+ writableEnded: false,
990
+ json(data) {
991
+ this._data = data;
992
+ this.writableEnded = true;
993
+ },
994
+ error(code, message) {
995
+ this._status = code;
996
+ this._data = { status: 'error', error: message };
997
+ this.writableEnded = true;
998
+ }
999
+ };
1000
+ return mock;
1001
+ }
1002
+
1003
+ /**
1004
+ * 写精简版 loop.json(单次执行任务专用)。
1005
+ *
1006
+ * 格式与 LoopEngine._persistLoopState 兼容,附加 isSingleExecution 标记。
1007
+ *
1008
+ * @param {string} taskId
1009
+ * @param {string} taskDescription
1010
+ * @param {string} taskDir
1011
+ * @param {string} skillDirectory
1012
+ * @param {string} status - executing | completed | failed | aborted
1013
+ * @param {string|null} startTime
1014
+ * @param {string|null} endTime
1015
+ * @private
1016
+ */
1017
+ async _writeSingleLoopJson(taskId, taskDescription, taskDir, skillDirectory, status, startTime, endTime) {
1018
+ if (!this._storage) return;
1019
+ try {
1020
+ // 映射 loop 状态到 orchestrator 状态(用于前端 rounds 渲染)
1021
+ var orchStatus;
1022
+ if (status === 'executing') orchStatus = 'running';
1023
+ else if (status === 'passed') orchStatus = 'completed';
1024
+ else if (status === 'aborted') orchStatus = 'aborted';
1025
+ else orchStatus = 'failed';
1026
+
1027
+ const state = {
1028
+ taskId,
1029
+ taskDescription,
1030
+ userAcceptanceCriteria: '',
1031
+ taskDir,
1032
+ skillDirectory,
1033
+ maxLoops: 1,
1034
+ currentLoop: 1,
1035
+ status,
1036
+ startTime: startTime || nowCST(),
1037
+ endTime,
1038
+ error: null,
1039
+ aborted: status === 'aborted',
1040
+ isSingleExecution: true,
1041
+ rounds: [{
1042
+ loop: 0,
1043
+ startTime: startTime || nowCST(),
1044
+ endTime,
1045
+ passed: status === 'passed',
1046
+ error: null,
1047
+ planningIsReplan: false,
1048
+ planningStepCount: 1,
1049
+ orchestratorStatus: orchStatus
1050
+ }],
1051
+ persistedAt: nowCST()
1052
+ };
1053
+ await this._storage.json('task').write(`${taskId}/loop`, state);
1054
+ } catch (err) {
1055
+ console.warn(`[FlexRunnerController] 写 loop.json 失败: taskId=${taskId}, ${err.message}`);
1056
+ }
1057
+ }
1058
+
1059
+ /**
1060
+ * 单次执行编排完成后的收尾处理。
1061
+ *
1062
+ * 1. 更新 loop.json 的终态状态
1063
+ * 2. 释放 Orchestrator 重型引用(FlexRunner/CLI/LLM 等)
1064
+ *
1065
+ * @param {string} taskId
1066
+ * @param {object} orch - Orchestrator 实例
1067
+ * @private
1068
+ */
1069
+ async _onSingleExecutionComplete(taskId, orch) {
1070
+ const status = orch.status;
1071
+
1072
+ // 映射编排终态到 loop.json 状态
1073
+ let loopStatus;
1074
+ if (status === OrchestratorStatus.COMPLETED) {
1075
+ loopStatus = 'passed';
1076
+ } else if (status === OrchestratorStatus.ABORTED) {
1077
+ loopStatus = 'aborted';
1078
+ } else {
1079
+ loopStatus = 'failed';
1080
+ }
1081
+
1082
+ const startTime = orch.startTime;
1083
+ const endTime = orch.endTime;
1084
+
1085
+ // 读取 taskDescription(从编排器获取)
1086
+ const progress = orch.getProgress();
1087
+ const step = progress.steps && progress.steps[0];
1088
+ const taskDescription = step ? step.taskDescription : '';
1089
+ const taskDir = orch._taskDir || '';
1090
+ const skillDirectory = orch._skillDirectory || '';
1091
+
1092
+ // 更新 loop.json
1093
+ await this._writeSingleLoopJson(
1094
+ taskId, taskDescription, taskDir, skillDirectory,
1095
+ loopStatus, startTime, endTime
1096
+ );
1097
+
1098
+ // 释放重型引用
1099
+ if (this._orchestratorController) {
1100
+ try {
1101
+ this._orchestratorController.disposeOrchestrator(taskId, { removeInstance: true });
1102
+ } catch (err) {
1103
+ console.warn(`[FlexRunnerController] 释放 Orchestrator 失败: taskId=${taskId}, ${err.message}`);
1104
+ }
1105
+ }
1106
+ }
1107
+
1108
+ /**
1109
+ * DELETE /api/flexrunner/runner?taskId=xxx&stepId=yyy
1110
+ * 移除一个已注册的 FlexRunner 实例
1111
+ */
1112
+ async removeRunner(req, res) {
1113
+ const taskId = req.query.taskId;
1114
+ const stepId = Number(req.query.stepId);
1115
+ const key = this._key(taskId, stepId);
1116
+ const existed = this._runners.delete(key);
1117
+ res.json({
1118
+ status: 'ok',
1119
+ removed: existed,
1120
+ message: existed ? '实例已移除' : '实例不存在',
1121
+ });
1122
+ }
1123
+ }
1124
+
1125
+ export default FlexRunnerController;