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