@ohos-cpf/3rdloop 0.0.1
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 +425 -0
- package/bin/3rdloop.mjs +48 -0
- package/lib/cli.js +741 -0
- package/lib/config.js +318 -0
- package/lib/doctor.js +114 -0
- package/lib/exit-codes.js +31 -0
- package/lib/opencode.js +156 -0
- package/lib/orch.js +427 -0
- package/lib/queries.js +264 -0
- package/lib/runner.js +979 -0
- package/lib/step.js +326 -0
- package/lib/ui.js +223 -0
- package/lib/workflow.js +291 -0
- package/lib/workflows.js +132 -0
- package/package.json +50 -0
- package/vendor/Server/Agent/SkillSelector/README.md +344 -0
- package/vendor/Server/Agent/SkillSelector/core/Agent.js +207 -0
- package/vendor/Server/Agent/SkillSelector/index.js +14 -0
- package/vendor/Server/Agent/SkillSelector/llm/llmClient.js +313 -0
- package/vendor/Server/Agent/SkillSelector/llm/prompts.js +161 -0
- package/vendor/Server/Agent/SkillSelector/llm/reactSteps.js +144 -0
- package/vendor/Server/Agent/SkillSelector/matcher/fuzzyMatcher.js +433 -0
- package/vendor/Server/Agent/SkillSelector/scanner/skillScanner.js +382 -0
- package/vendor/Server/Agent/SkillSelector/types.js +63 -0
- package/vendor/Server/Agent/SkillSelector/utils/actionWords.js +60 -0
- package/vendor/Server/Brain/AnalysisStrategies.js +422 -0
- package/vendor/Server/Brain/Brain.js +686 -0
- package/vendor/Server/Brain/DecompositionStrategies.js +621 -0
- package/vendor/Server/Brain/PlanPersistence.js +196 -0
- package/vendor/Server/Brain/SkillExecutor.js +251 -0
- package/vendor/Server/Brain/index.js +11 -0
- package/vendor/Server/CLI/cli.js +243 -0
- package/vendor/Server/CLI/opencode/index.js +804 -0
- package/vendor/Server/CLI/opencode.js +5 -0
- package/vendor/Server/DbUse/MessageArchive.js +61 -0
- package/vendor/Server/DbUse/StorageManager.js +107 -0
- package/vendor/Server/DbUse/adapters/BaseAdapter.js +94 -0
- package/vendor/Server/DbUse/adapters/FileAdapter.js +138 -0
- package/vendor/Server/DbUse/adapters/JsonAdapter.js +138 -0
- package/vendor/Server/DbUse/adapters/JsonlAdapter.js +114 -0
- package/vendor/Server/DbUse/adapters/SqliteAdapter.js +157 -0
- package/vendor/Server/DbUse/index.js +160 -0
- package/vendor/Server/DbUse/stores/SessionStore.js +279 -0
- package/vendor/Server/DbUse/stores/TaskExecutionStore.js +133 -0
- package/vendor/Server/DbUse/stores/TaskRegistryStore.js +262 -0
- package/vendor/Server/DbUse/stores/TaskStepStore.js +279 -0
- package/vendor/Server/FlexRunner/FlexRunner.js +1759 -0
- package/vendor/Server/FlexRunner/index.js +12 -0
- package/vendor/Server/Knowledge/KnowledgeImporter.js +523 -0
- package/vendor/Server/Knowledge/KnowledgeManager.js +341 -0
- package/vendor/Server/Knowledge/index.js +33 -0
- package/vendor/Server/LoopEngine/LoopEngine.js +1133 -0
- package/vendor/Server/LoopEngine/index.js +11 -0
- package/vendor/Server/Orchestrator/Orchestrator.js +1425 -0
- package/vendor/Server/Orchestrator/StepNode.js +312 -0
- package/vendor/Server/Orchestrator/index.js +13 -0
- package/vendor/Server/Routes/controllers/BrainController.js +241 -0
- package/vendor/Server/Routes/controllers/FlexRunnerController.js +1125 -0
- package/vendor/Server/Routes/controllers/HealthController.js +44 -0
- package/vendor/Server/Routes/controllers/KnowledgeImportController.js +355 -0
- package/vendor/Server/Routes/controllers/LoopEngineController.js +788 -0
- package/vendor/Server/Routes/controllers/OrchestratorController.js +911 -0
- package/vendor/Server/Routes/controllers/StatsController.js +57 -0
- package/vendor/Server/Routes/controllers/StreamController.js +67 -0
- package/vendor/Server/Routes/library/logger.js +179 -0
- package/vendor/Server/Routes/library/zipArchive.js +172 -0
- package/vendor/Server/Routes/router/Router.js +204 -0
- package/vendor/Server/Routes/routes/brain.js +47 -0
- package/vendor/Server/Routes/routes/flexrunner.js +72 -0
- package/vendor/Server/Routes/routes/health.js +28 -0
- package/vendor/Server/Routes/routes/index.js +57 -0
- package/vendor/Server/Routes/routes/knowledge.js +55 -0
- package/vendor/Server/Routes/routes/loopengine.js +58 -0
- package/vendor/Server/Routes/routes/orchestrator.js +61 -0
- package/vendor/Server/Routes/routes/registry.js +98 -0
- package/vendor/Server/Routes/routes/stats.js +23 -0
- package/vendor/Server/Routes/routes/stream.js +30 -0
- package/vendor/Server/Routes/server.js +202 -0
- package/vendor/Server/Routes/sse/SseHub.js +308 -0
- package/vendor/Server/Skills/acceptance-check/SKILL.md +174 -0
- package/vendor/Server/Skills/acceptance-check/references/PHASE1_ARTIFACT_COLLECTION.md +52 -0
- package/vendor/Server/Skills/acceptance-check/references/PHASE2_CRITERIA_EVALUATION.md +77 -0
- package/vendor/Server/Skills/arkts-code-check/SKILL.md +314 -0
- package/vendor/Server/Skills/arkts-code-check/references/CODE_CHECK_GUIDE.md +160 -0
- package/vendor/Server/Skills/arkts-code-check/references/FILE_TYPE_GUIDE.md +29 -0
- package/vendor/Server/Skills/arkts-code-check/references/PHASE1_INFO_COLLECTION.md +222 -0
- package/vendor/Server/Skills/arkts-code-check/references/REPORT_TEMPLATE.md +220 -0
- package/vendor/Server/Skills/arkts-library-compile-validate/SKILL.md +329 -0
- package/vendor/Server/Skills/arkts-library-compile-validate/scripts/build.ps1 +93 -0
- package/vendor/Server/Skills/arkts-library-compile-validate/scripts/code-linter.cjs +433 -0
- package/vendor/Server/Skills/arkts-library-compile-validate/scripts/migrate.cjs +851 -0
- package/vendor/Server/Skills/arkts-library-compile-validate/scripts/ohpm-switch.cjs +789 -0
- package/vendor/Server/Skills/arkts-library-demo-code/SKILL.md +291 -0
- package/vendor/Server/Skills/arkts-library-demo-code/references/API_SIGNATURE_CHECKS.md +68 -0
- package/vendor/Server/Skills/arkts-library-demo-code/references/BUILD_VALIDATION.md +184 -0
- package/vendor/Server/Skills/arkts-library-demo-code/references/CODE_STANDARDS.md +337 -0
- package/vendor/Server/Skills/arkts-library-demo-code/references/EXAMPLES.md +400 -0
- package/vendor/Server/Skills/arkts-library-demo-code/references/SYSTEM_API_LOOKUP.md +99 -0
- package/vendor/Server/Skills/arkts-library-demo-code/references/TEMPLATE.md +544 -0
- package/vendor/Server/Skills/arkts-library-demo-code/scripts/build.ps1 +93 -0
- package/vendor/Server/Skills/arkts-library-demo-code/scripts/code-linter.cjs +433 -0
- package/vendor/Server/Skills/arkts-library-demo-code/scripts/migrate.cjs +851 -0
- package/vendor/Server/Skills/arkts-library-demo-code/scripts/ohpm-switch.cjs +789 -0
- package/vendor/Server/Skills/arkts-library-demo-coverage/SKILL.md +174 -0
- package/vendor/Server/Skills/arkts-library-demo-scenario/SKILL.md +321 -0
- package/vendor/Server/Skills/arkts-library-demo-scenario/references/EXAMPLES.md +100 -0
- package/vendor/Server/Skills/arkts-library-demo-scenario/references/TEMPLATE.md +185 -0
- package/vendor/Server/Skills/arkts-library-documentation-quality-check/SKILL.md +234 -0
- package/vendor/Server/Skills/arkts-library-documentation-quality-check/assets/REPORT_TEMPLATE.md +103 -0
- package/vendor/Server/Skills/arkts-library-documentation-quality-check/references/CHECK_DIMENSIONS.md +89 -0
- package/vendor/Server/Skills/arkts-library-documentation-quality-check/references/COMPLIANCE_CHECKLIST.md +62 -0
- package/vendor/Server/Skills/arkts-library-documentation-quality-check/references/KB_QUERY_GUIDE.md +108 -0
- package/vendor/Server/Skills/arkts-library-documentation-quality-check/references/MCP_TOOL_DISCOVERY.md +124 -0
- package/vendor/Server/Skills/arkts-library-documentation-quality-check/references/REPORT_TEMPLATE.md +90 -0
- package/vendor/Server/Skills/arkts-library-documentation-quality-check/references/SCORING_FORMULA.md +57 -0
- package/vendor/Server/Skills/arkts-library-documentation-quality-check/scripts/validate-skill.cjs +259 -0
- package/vendor/Server/Skills/arkts-library-interface-spec/SKILL.md +97 -0
- package/vendor/Server/Skills/arkts-library-memory-leak-check/SKILL.md +392 -0
- package/vendor/Server/Skills/arkts-library-source-clone/SKILL.md +113 -0
- package/vendor/Server/Skills/arkts-library-stability-check/SKILL.md +313 -0
- package/vendor/Server/Skills/arkts-library-stability-check/assets/REPORT_TEMPLATE.md +154 -0
- package/vendor/Server/Skills/arkts-library-stability-check/references/CHECKLIST.md +166 -0
- package/vendor/Server/Skills/arkts-library-stability-check/references/CODE_EXAMPLES.md +309 -0
- package/vendor/Server/Skills/arkts-library-stability-check/references/SCORING_RULES.md +90 -0
- package/vendor/Server/Skills/arkts-library-stability-check/scripts/validate-skill.cjs +282 -0
- package/vendor/Server/Skills/arkts-library-test-coverage-check/SKILL.md +361 -0
- package/vendor/Server/Skills/arkts-library-test-coverage-check/assets/REPORT_TEMPLATE.md +146 -0
- package/vendor/Server/Skills/arkts-library-test-coverage-check/references/CHECKLIST.md +253 -0
- package/vendor/Server/Skills/arkts-library-test-coverage-check/references/MCP_TOOL_USAGE.md +115 -0
- package/vendor/Server/Skills/arkts-library-test-coverage-check/scripts/validate-skill.cjs +263 -0
- package/vendor/Server/Skills/arkts-library-xts-code/SKILL.md +455 -0
- package/vendor/Server/Skills/arkts-library-xts-code/assets/report-template.md +60 -0
- package/vendor/Server/Skills/arkts-library-xts-code/references/ACCESS_MODIFIER_PRECHECK.md +76 -0
- package/vendor/Server/Skills/arkts-library-xts-code/references/BUILD_VERIFICATION.md +200 -0
- package/vendor/Server/Skills/arkts-library-xts-code/references/CODE_STANDARDS.md +519 -0
- package/vendor/Server/Skills/arkts-library-xts-code/references/SUBAGENT_PROMPT_TEMPLATE.md +276 -0
- package/vendor/Server/Skills/arkts-library-xts-code/references/SYSTEM_API_LOOKUP.md +93 -0
- package/vendor/Server/Skills/arkts-library-xts-code/references/XTS_TEMPLATE.md +767 -0
- package/vendor/Server/Skills/arkts-library-xts-code/scripts/build.ps1 +93 -0
- package/vendor/Server/Skills/arkts-library-xts-code/scripts/code-linter.cjs +433 -0
- package/vendor/Server/Skills/arkts-library-xts-code/scripts/migrate.cjs +851 -0
- package/vendor/Server/Skills/arkts-library-xts-code/scripts/ohpm-switch.cjs +789 -0
- package/vendor/Server/Skills/arkts-library-xts-code/scripts/precheck-access-modifiers.cjs +221 -0
- package/vendor/Server/Skills/arkts-library-xts-code-fix/SKILL.md +229 -0
- package/vendor/Server/Skills/arkts-library-xts-coverage/SKILL.md +97 -0
- package/vendor/Server/Skills/arkts-library-xts-doc/SKILL.md +194 -0
- package/vendor/Server/Skills/arkts-library-xts-doc/references/EXAMPLES.md +576 -0
- package/vendor/Server/Skills/arkts-library-xts-doc/references/TEMPLATE.md +575 -0
- package/vendor/Server/Skills/arkts-library-xts-doc/references/TESTABILITY_GUIDE.md +182 -0
- package/vendor/Server/Skills/arkts-library-xts-execution/SKILL.md +491 -0
- package/vendor/Server/Skills/arkts-library-xts-execution/references/error-fixing-examples.md +217 -0
- package/vendor/Server/Skills/arkts-library-xts-execution/scripts/build.ps1 +93 -0
- package/vendor/Server/Skills/arkts-library-xts-execution/scripts/code-linter.cjs +433 -0
- package/vendor/Server/Skills/arkts-library-xts-execution/scripts/migrate.cjs +851 -0
- package/vendor/Server/Skills/arkts-library-xts-execution/scripts/ohpm-switch.cjs +789 -0
- package/vendor/Server/Skills/flutter-code-check/SKILL.md +299 -0
- package/vendor/Server/Skills/flutter-code-check/assets/OUTPUT_TEMPLATE.md +312 -0
- package/vendor/Server/Skills/flutter-code-check/references/CHECKLIST.md +190 -0
- package/vendor/Server/Skills/flutter-code-check/references/PLATFORMS.md +134 -0
- package/vendor/Server/Skills/flutter-code-check/scripts/validate-skill.cjs +259 -0
- package/vendor/Server/Skills/flutter-interface/README.md +75 -0
- package/vendor/Server/Skills/flutter-interface/SKILL.md +291 -0
- package/vendor/Server/Skills/flutter-interface/references/EXAMPLES.md +336 -0
- package/vendor/Server/Skills/flutter-interface/references/GIT_CLONE_GUIDE.md +190 -0
- package/vendor/Server/Skills/flutter-interface/references/SCRIPT_REFERENCE.md +258 -0
- package/vendor/Server/Skills/flutter-interface/references/TEMPLATE.md +254 -0
- package/vendor/Server/Skills/flutter-library-consistency-check/SKILL.md +518 -0
- package/vendor/Server/Skills/flutter-library-consistency-check/references/CHECKLIST.md +382 -0
- package/vendor/Server/Skills/flutter-library-consistency-check/references/FUNCTION_COMPARISON_GUIDE.md +342 -0
- package/vendor/Server/Skills/flutter-library-consistency-check/references/PLATFORM_LIMITATION_GUIDE.md +71 -0
- package/vendor/Server/Skills/flutter-library-documentation-quality-check/SKILL.md +519 -0
- package/vendor/Server/Skills/flutter-library-documentation-quality-check/references/CHECKLIST.md +407 -0
- package/vendor/Server/Skills/flutter-library-memory-leak-check/SKILL.md +653 -0
- package/vendor/Server/Skills/flutter-library-memory-leak-check/references/CHECKLIST.md +397 -0
- package/vendor/Server/Skills/flutter-library-stability-check/SKILL.md +745 -0
- package/vendor/Server/Skills/flutter-library-stability-check/references/CHECKLIST.md +721 -0
- package/vendor/Server/Skills/flutter-library-test-coverage-check/SKILL.md +414 -0
- package/vendor/Server/Skills/flutter-library-test-coverage-check/assets/REPORT_TEMPLATE.md +150 -0
- package/vendor/Server/Skills/flutter-library-test-coverage-check/references/CHECKLIST.md +304 -0
- package/vendor/Server/Skills/flutter-library-test-coverage-check/references/SCORING_RULES.md +265 -0
- package/vendor/Server/Skills/flutter-library-test-coverage-check/references/SCRIPT_USAGE.md +286 -0
- package/vendor/Server/Skills/knowledge-import/SKILL.md +383 -0
- package/vendor/Server/Skills/knowledge-import/references/MCP_TOOL_USAGE.md +165 -0
- package/vendor/Server/Skills/knowledge-import/references/SKILL_LOCATOR_GUIDE.md +91 -0
- package/vendor/Server/Skills/knowledge-import/references/SKILL_OPTIMIZATION_GUIDE.md +127 -0
- package/vendor/Server/Skills/ohos-lib-pr-push/SKILL.md +301 -0
- package/vendor/Server/Skills/ohos-lib-pr-push/assets/PR_ISSUE_TEMPLATE.md +105 -0
- package/vendor/Server/Skills/ohos-lib-pr-push/references/GITCODE_API.md +127 -0
- package/vendor/Server/Skills/ohos-lib-pr-push/scripts/gitcode-api.cjs +208 -0
- package/vendor/Server/Skills/rn-code-check/SKILL.md +321 -0
- package/vendor/Server/Skills/rn-code-check/references/API-GUIDE.md +235 -0
- package/vendor/Server/Skills/rn-code-check/references/CHECKLIST.md +189 -0
- package/vendor/Server/Skills/rn-code-check/references/REPORT-TEMPLATE.md +247 -0
- package/vendor/Server/Skills/rn-lib-add-example/SKILL.md +198 -0
- package/vendor/Server/Skills/rn-lib-add-example/references/APP_RENAME_GUIDE.md +40 -0
- package/vendor/Server/Skills/rn-lib-add-example/references/SHELL_ADAPTATION_CHECKLIST.md +78 -0
- package/vendor/Server/Skills/rn-lib-add-example/references/TEMPLATE_REPO_GUIDE.md +70 -0
- package/vendor/Server/Skills/rn-lib-add-example/references/VERIFICATION_GUIDE.md +47 -0
- package/vendor/Server/Skills/rn-lib-add-example/scripts/create-example.cjs +252 -0
- package/vendor/Server/Skills/rn-lib-update-doc/SKILL.md +176 -0
- package/vendor/Server/Skills/rn-lib-update-doc/config.json +5 -0
- package/vendor/Server/Skills/rn-lib-update-doc/references/changelog-template.md +35 -0
- package/vendor/Server/Skills/rn-lib-update-doc/references/format-diff.md +137 -0
- package/vendor/Server/Skills/rn-lib-update-doc/references/link-registry.json +33 -0
- package/vendor/Server/Skills/rn-lib-update-doc/references/model-0.4.2-overview.md +40 -0
- package/vendor/Server/Skills/rn-lib-update-doc/references/model-0.4.2.md +429 -0
- package/vendor/Server/Skills/rn-lib-update-doc/scripts/detect-readme-format.cjs +105 -0
- package/vendor/Server/Skills/rn-lib-update-doc/scripts/find-usage-doc.cjs +90 -0
- package/vendor/Server/Skills/rn-lib-update-doc/scripts/link-verify.cjs +213 -0
- package/vendor/Server/Skills/rn-lib-update-doc/scripts/normalize-changelog.cjs +73 -0
- package/vendor/Server/Skills/rn-lib-update-doc/scripts/normalize-package-meta.cjs +258 -0
- package/vendor/Server/Skills/rn-lib-update-doc/scripts/normalize-readme-links.cjs +341 -0
- package/vendor/Server/Skills/rn-lib-update-doc/scripts/resolve-usage-docs.cjs +67 -0
- package/vendor/Server/Skills/rn-library-consistency-check/SKILL.md +299 -0
- package/vendor/Server/Skills/rn-library-consistency-check/assets/REPORT_TEMPLATE.md +293 -0
- package/vendor/Server/Skills/rn-library-consistency-check/references/ARKTS_COMPONENT_CHECK_GUIDE.md +857 -0
- package/vendor/Server/Skills/rn-library-consistency-check/references/ARKTS_TURBOMODULE_CHECK_GUIDE.md +856 -0
- package/vendor/Server/Skills/rn-library-consistency-check/references/CHECKLIST.md +157 -0
- package/vendor/Server/Skills/rn-library-consistency-check/references/CPP_COMPONENT_CHECK_GUIDE.md +801 -0
- package/vendor/Server/Skills/rn-library-consistency-check/references/CPP_TURBOMODULE_CHECK_GUIDE.md +684 -0
- package/vendor/Server/Skills/rn-library-consistency-check/references/FUNCTION_COMPARISON_GUIDE.md +233 -0
- package/vendor/Server/Skills/rn-library-consistency-check/references/NATIVE_COMPONENT_IDENTIFICATION.md +1373 -0
- package/vendor/Server/Skills/rn-library-consistency-check/references/SCORING_STANDARD.md +220 -0
- package/vendor/Server/Skills/rn-library-consistency-check/scripts/validate-skill.py +245 -0
- package/vendor/Server/Skills/rn-library-documentation-quality-check/SKILL.md +263 -0
- package/vendor/Server/Skills/rn-library-documentation-quality-check/assets/MODEL.md +414 -0
- package/vendor/Server/Skills/rn-library-documentation-quality-check/assets/MODEL_en.md +413 -0
- package/vendor/Server/Skills/rn-library-documentation-quality-check/assets/REPORT_TEMPLATE.md +311 -0
- package/vendor/Server/Skills/rn-library-documentation-quality-check/references/CHECK_CHECKLIST.md +355 -0
- package/vendor/Server/Skills/rn-library-documentation-quality-check/references/CROSS_VALIDATION_GUIDE.md +115 -0
- package/vendor/Server/Skills/rn-library-documentation-quality-check/references/DOC_COLLECTION_GUIDE.md +139 -0
- package/vendor/Server/Skills/rn-library-documentation-quality-check/references/EXAMPLES.md +586 -0
- package/vendor/Server/Skills/rn-library-documentation-quality-check/references/GOTCHAS.md +57 -0
- package/vendor/Server/Skills/rn-library-documentation-quality-check/scripts/validate-skill.py +259 -0
- package/vendor/Server/Skills/rn-library-interface-analyzer/SKILL.md +301 -0
- package/vendor/Server/Skills/rn-library-interface-analyzer/assets/REPORT_TEMPLATE.md +148 -0
- package/vendor/Server/Skills/rn-library-interface-analyzer/references/DOC_ANNOTATION_GUIDE.md +118 -0
- package/vendor/Server/Skills/rn-library-interface-analyzer/references/SCRIPT_USAGE_GUIDE.md +168 -0
- package/vendor/Server/Skills/rn-library-interface-analyzer/references/URL_PARSING_RULES.md +176 -0
- package/vendor/Server/Skills/rn-library-issue-generalizer/SKILL.md +274 -0
- package/vendor/Server/Skills/rn-library-issue-generalizer/assets/case-template.md +45 -0
- package/vendor/Server/Skills/rn-library-issue-generalizer/assets/rule-template.md +25 -0
- package/vendor/Server/Skills/rn-library-issue-generalizer/references/PR_FETCH_GUIDE.md +61 -0
- package/vendor/Server/Skills/rn-library-issue-generalizer/references/RULE_TEMPLATE_GUIDE.md +165 -0
- package/vendor/Server/Skills/rn-library-issue-generalizer/scripts/fetch-pr-info.py +239 -0
- package/vendor/Server/Skills/rn-library-known-issue-check/SKILL.md +263 -0
- package/vendor/Server/Skills/rn-library-known-issue-check/assets/REPORT_TEMPLATE.md +96 -0
- package/vendor/Server/Skills/rn-library-known-issue-check/references/COMPLIANCE_CHECKLIST.md +58 -0
- package/vendor/Server/Skills/rn-library-known-issue-check/references/INSPECTION_GUIDE.md +99 -0
- package/vendor/Server/Skills/rn-library-known-issue-check/references/PRESCREEN_GUIDE.md +64 -0
- package/vendor/Server/Skills/rn-library-known-issue-check/references/SOURCE_PREP_GUIDE.md +75 -0
- package/vendor/Server/Skills/rn-library-known-issue-check/scripts/validate-known-issue-report.py +164 -0
- package/vendor/Server/Skills/rn-library-memory-leak-check/SKILL.md +339 -0
- package/vendor/Server/Skills/rn-library-memory-leak-check/assets/REPORT_TEMPLATE.md +83 -0
- package/vendor/Server/Skills/rn-library-memory-leak-check/references/CHECKLIST.md +859 -0
- package/vendor/Server/Skills/rn-library-memory-leak-check/references/HARMONY_CODE_PATTERNS.md +122 -0
- package/vendor/Server/Skills/rn-library-memory-leak-check/scripts/scan_harmony_code.py +279 -0
- package/vendor/Server/Skills/rn-library-stability-check/SKILL.md +273 -0
- package/vendor/Server/Skills/rn-library-stability-check/assets/REPORT_TEMPLATE.md +129 -0
- package/vendor/Server/Skills/rn-library-stability-check/references/CHECKLIST.md +581 -0
- package/vendor/Server/Skills/rn-library-stability-check/references/SCORING_RULES.md +138 -0
- package/vendor/Server/Skills/rn-library-stability-check/scripts/scan-harmony-code.py +168 -0
- package/vendor/Server/Skills/rn-library-test-coverage-check/SKILL.md +506 -0
- package/vendor/Server/Skills/rn-library-test-coverage-check/references/CHECKLIST.md +280 -0
- package/vendor/Server/Skills/rn-library-test-coverage-check/references/DEMO_LOCATION.md +221 -0
- package/vendor/Server/Skills/rn-library-test-coverage-check/references/REPORT_TEMPLATE.md +209 -0
- package/vendor/Server/Skills/rn-library-test-coverage-check/references/SCRIPT_USAGE.md +325 -0
- package/vendor/Server/Skills/rn-ohos-xts-generator/SKILL.md +279 -0
- package/vendor/Server/Skills/rn-ohos-xts-generator/assets/TEST_FILE_TEMPLATES.md +404 -0
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/BUILD_TROUBLESHOOTING.md +185 -0
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/BUILD_WORKFLOW.md +265 -0
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/COMMON_CONFIG.md +189 -0
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/CONFIG_GUIDE.md +203 -0
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/COVERAGE_ANALYZER.md +147 -0
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/DESIGN_DOC_GUIDE.md +148 -0
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/GENERATION_FLOW.md +165 -0
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/GENERATION_STRATEGY.md +195 -0
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/HARMONY_SCANNER.md +249 -0
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/JEST_FRAMEWORK.md +235 -0
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/PLATFORM_COMPAT.md +161 -0
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/RN_TEST_PATTERNS.md +413 -0
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/SOURCE_PARSER.md +235 -0
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/TEST_CONVENTIONS.md +245 -0
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/TROUBLESHOOTING.md +282 -0
- package/vendor/Server/Skills/rn-ohos-xts-generator/references/USAGE_GUIDE.md +251 -0
- package/vendor/Server/Skills/rnoh-cptf-api-test/SKILL.md +469 -0
- package/vendor/Server/Skills/rnoh-cptf-api-test/assets/CPTF_TEST_TEMPLATE.md +310 -0
- package/vendor/Server/Skills/rnoh-cptf-api-test/assets/DEMO_PAGE_TEMPLATE.md +303 -0
- package/vendor/Server/Skills/rnoh-cptf-api-test/references/CPTF_TEST_GENERATION_GUIDE.md +421 -0
- package/vendor/Server/Skills/rnoh-cptf-api-test/references/DEMO_GENERATION_GUIDE.md +358 -0
- package/vendor/Server/Skills/rnoh-cptf-api-test/references/QUALITY_CHECKLIST.md +114 -0
- package/vendor/Server/Skills/rnoh-cptf-api-test/references/SPEC_PARSING_GUIDE.md +274 -0
- package/vendor/Server/Skills/rnoh-cptf-api-test/scripts/validate-rnoh-cptf-api-test.sh +185 -0
- package/vendor/Server/Skills/rnoh-cptf-demo-test/SKILL.md +191 -0
- package/vendor/Server/Skills/rnoh-cptf-demo-test/assets/TEST_TEMPLATE.md +216 -0
- package/vendor/Server/Skills/rnoh-cptf-demo-test/references/CPTF_SETUP.md +107 -0
- package/vendor/Server/Skills/rnoh-cptf-demo-test/references/DEMO_ANALYSIS.md +120 -0
- package/vendor/Server/Skills/rnoh-cptf-demo-test/scripts/validate-rnoh-cptf-test.sh +108 -0
- package/vendor/Server/Skills/rnoh-cptf-test-run/SKILL.md +237 -0
- package/vendor/Server/Skills/rnoh-cptf-test-run/references/DEBUG_PLAYBOOK.md +261 -0
- package/vendor/Server/Skills/rnoh-cptf-test-run/scripts/validate-run-env.sh +112 -0
- package/vendor/Server/Skills/rnoh-lib-code-adapt/SKILL.md +498 -0
- package/vendor/Server/Skills/rnoh-lib-code-adapt/references/adapt-checklist.md +270 -0
- package/vendor/Server/Skills/rnoh-lib-code-adapt/references/api-migration-checklist.md +164 -0
- package/vendor/Server/Skills/rnoh-lib-code-adapt/references/common-compile-errors.md +556 -0
- package/vendor/Server/Skills/rnoh-lib-code-adapt/references/deveco-cli-tools.md +459 -0
- package/vendor/Server/Skills/rnoh-lib-code-adapt/references/edge-cases.md +65 -0
- package/vendor/Server/Skills/rnoh-lib-code-adapt/references/har-tgz-build.md +512 -0
- package/vendor/Server/Skills/rnoh-lib-code-adapt/references/version-compat-matrix.md +132 -0
- package/vendor/Server/Skills/rnoh-lib-code-adapt/scripts/auto-integrate.sh +424 -0
- package/vendor/Server/Skills/rnoh-lib-demo-coverage/SKILL.md +137 -0
- package/vendor/Server/Skills/rnoh-lib-demo-create/SKILL.md +306 -0
- package/vendor/Server/Skills/rnoh-lib-demo-create/assets/DEMO_PAGE_TEMPLATE.md +485 -0
- package/vendor/Server/Skills/rnoh-lib-demo-create/assets/INDEX_TEMPLATE.md +172 -0
- package/vendor/Server/Skills/rnoh-lib-demo-create/references/DEMO_CODE_RULES.md +422 -0
- package/vendor/Server/Skills/rnoh-lib-demo-create/references/QUALITY_CHECKLIST.md +95 -0
- package/vendor/Server/Skills/rnoh-lib-demo-create/references/SOURCE_EXPLORATION.md +169 -0
- package/vendor/Server/Skills/rnoh-lib-demo-doc/SKILL.md +554 -0
- package/vendor/Server/Skills/rnoh-lib-demo-doc/assets/DEMO_DOC_TEMPLATE.md +106 -0
- package/vendor/Server/Skills/rnoh-lib-demo-doc/references/COVERAGE_INCREMENTAL.md +216 -0
- package/vendor/Server/Skills/rnoh-lib-demo-doc/references/DEMO_DESIGN_RULES.md +413 -0
- package/vendor/Server/Skills/rnoh-lib-demo-doc/references/OUTPUT_FORMAT.md +269 -0
- package/vendor/Server/Skills/rnoh-lib-demo-doc/references/QUALITY_CHECKLIST.md +98 -0
- package/vendor/Server/Skills/rnoh-lib-func-test/SKILL.md +337 -0
- package/vendor/Server/Skills/rnoh-lib-rnt-search/SKILL.md +722 -0
- package/vendor/Server/Skills/rnoh-lib-rnt-search/assets/PORT_REPORT_TEMPLATE.md +219 -0
- package/vendor/Server/Skills/rnoh-lib-rnt-search/references/DEMO_PORTING_GUIDE.md +345 -0
- package/vendor/Server/Skills/rnoh-lib-rnt-search/references/PORT_VERIFICATION.md +269 -0
- package/vendor/Server/Skills/rnoh-lib-rnt-search/references/RNT_PROJECT_STRUCTURE.md +173 -0
- package/vendor/Server/Skills/rnoh-lib-rnt-search/scripts/analyze-demo-deps.cjs +267 -0
- package/vendor/Server/Skills/rnoh-lib-rnt-search/scripts/detect-rnoh-version.cjs +174 -0
- package/vendor/Server/Skills/rnoh-lib-rnt-search/scripts/find-rnt-demo.sh +111 -0
- package/vendor/Server/Skills/rnoh-lib-rnt-search/scripts/port-demo.sh +186 -0
- package/vendor/Server/Skills/rnoh-lib-rnt-search/scripts/read-lib-meta.cjs +150 -0
- package/vendor/Server/Skills/rnoh-lib-test-align/SKILL.md +296 -0
- package/vendor/Server/Skills/rnoh-lib-test-align/references/jest-troubleshooting.md +371 -0
- package/vendor/Server/Skills/rnoh-lib-test-align/references/port-test-demo-guide.md +212 -0
- package/vendor/Server/Skills/rnoh-lib-test-align/references/runtime-troubleshooting.md +345 -0
- package/vendor/Server/Skills/rnoh-lib-test-align/references/unit-test-template.md +223 -0
- package/vendor/Server/Skills/rnoh-lib-test-align/scripts/port-test-demo.sh +1053 -0
- package/vendor/Server/Skills/rnoh-lib-version-diff/SKILL.md +255 -0
- package/vendor/Server/Skills/rnoh-lib-version-diff/references/OHOS_ADAPTATION.md +77 -0
- package/vendor/Server/Skills/rnoh-lib-version-diff/references/OHOS_API_LOOKUP.md +224 -0
- package/vendor/Server/Skills/rnoh-lib-version-diff/references/script-reference.md +174 -0
- package/vendor/Server/Skills/rnoh-lib-version-diff/scripts/detect-upstream-version.js +967 -0
- package/vendor/Server/Skills/rnoh-lib-version-diff/scripts/generate-diff.js +1798 -0
- package/vendor/Server/Skills/rnoh-lib-version-diff/scripts/package.json +5 -0
- package/vendor/Server/Skills/rnoh-lib-version-diff/templates/report-template.md +137 -0
- package/vendor/Server/Skills/rnoh-lib-xts-coverage/SKILL.md +145 -0
- package/vendor/Server/Skills/skill-generator/SKILL.md +365 -0
- package/vendor/Server/Skills/skill-generator/assets/SKILL_TEMPLATE.md +183 -0
- package/vendor/Server/Skills/skill-generator/references/COMPLIANCE_CHECKLIST.md +82 -0
- package/vendor/Server/Skills/skill-generator/references/KB_QUERY_GUIDE.md +110 -0
- package/vendor/Server/Skills/skill-generator/references/MCP_TOOL_DISCOVERY.md +122 -0
- package/vendor/Server/Skills/skill-generator/references/SKILL_SPEC.md +121 -0
- package/vendor/Server/Skills/skill-generator/scripts/validate-skill.cjs +259 -0
- package/vendor/Server/Skills/task-analysis/SKILL.md +234 -0
- package/vendor/Server/Skills/task-analysis/assets/OUTPUT_EXAMPLES.md +160 -0
- package/vendor/Server/Skills/task-analysis/references/ACCEPTANCE_CRITERIA_GUIDE.md +190 -0
- package/vendor/Server/Skills/task-analysis/references/ANALYSIS_SCHEMA.md +224 -0
- package/vendor/Server/Skills/task-analysis/references/INTEGRATION_CONTEXT.md +162 -0
- package/vendor/Server/Skills/task-analysis/references/LESSONS_LEARNED.md +55 -0
- package/vendor/Server/Skills/task-analysis/references/REPLAN_CONTEXT_GUIDE.md +126 -0
- package/vendor/Server/Skills/task-analysis/scripts/validate-analysis-output.sh +256 -0
- package/vendor/Server/Skills/task-decomposition/SKILL.md +529 -0
- package/vendor/Server/Skills/task-decomposition/assets/OUTPUT_EXAMPLES.md +451 -0
- package/vendor/Server/Skills/task-decomposition/references/ACCEPTANCE_CRITERIA_GUIDE.md +179 -0
- package/vendor/Server/Skills/task-decomposition/references/DEPENDENCY_GRAPH_GUIDE.md +152 -0
- package/vendor/Server/Skills/task-decomposition/references/INTEGRATION_CONTEXT.md +175 -0
- package/vendor/Server/Skills/task-decomposition/references/REPLAN_CONTEXT_GUIDE.md +195 -0
- package/vendor/Server/Skills/task-decomposition/references/TASK_DECOMPOSITION_TEMPLATES.md +226 -0
- package/vendor/Server/Skills/task-decomposition/scripts/list-available-skills.cjs +263 -0
- package/vendor/Server/Skills/task-decomposition/scripts/skill-blacklist.json +29 -0
- package/vendor/Server/Skills/task-decomposition/scripts/validate-decomposition-output.cjs +376 -0
- package/vendor/Server/Skills/task-decomposition/scripts/validate-param-consistency.cjs +259 -0
- package/vendor/Server/Skills/task-decomposition/scripts/validate-specified-skills.cjs +216 -0
- package/vendor/Server/Skills/task-decomposition/scripts/validate-step-graph.cjs +425 -0
- package/vendor/Server/Skills/tpc-cpp-check/SKILL.md +427 -0
- package/vendor/Server/Skills/tpc-cpp-check/references/CHECKLIST.md +214 -0
- package/vendor/Server/Skills/tpc-cpp-check/references/HPKBUILD_GUIDE.md +235 -0
- package/vendor/Server/Skills/tpc-cpp-check/references/REPORT_TEMPLATE.md +250 -0
- package/vendor/Server/TestCheck/TestCheck.js +775 -0
- package/vendor/Server/TestCheck/index.js +11 -0
- package/vendor/Server/library/McpToolGuide.js +280 -0
- package/vendor/Server/library/StatsService.js +127 -0
- package/vendor/Server/library/cstTime.js +16 -0
- package/vendor/Server/package.json +17 -0
- package/vendor/VERSION +3 -0
- package/workflows/pr-check.mjs +106 -0
|
@@ -0,0 +1,1425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrator —— FlexRunner 任务编排器
|
|
3
|
+
*
|
|
4
|
+
* 接收一段规定好任务的 JSON(taskId + 多个 step),按照依赖关系编排执行:
|
|
5
|
+
* - 无依赖的步骤并行执行
|
|
6
|
+
* - 成功 → nextStepId,失败 → failStepId(未达 maxRetries 时跳到修复步骤)
|
|
7
|
+
* - 将前一步的结果报告路径传递给后续步骤
|
|
8
|
+
* - 全局执行上限 GLOBAL_EXEC_LIMIT 兜底,防止链路形成无限循环
|
|
9
|
+
* - 死锁检测:所有步骤在等待但无人可执行时终止
|
|
10
|
+
* - 实时进度推送:通过 SseHub 向前端广播状态变更
|
|
11
|
+
* - 未执行步骤支持配置修改
|
|
12
|
+
*
|
|
13
|
+
* 设计原则:
|
|
14
|
+
* - 高内聚:编排逻辑集中在 Orchestrator,步骤状态在 StepNode,FlexRunner 负责执行
|
|
15
|
+
* - 低耦合:通过构造注入 SseHub / FlexRunner 工厂 / 存储,不硬编码依赖
|
|
16
|
+
* - 单一职责:Orchestrator 只管调度和状态流转,不关心 Skill 匹配和 CLI 细节
|
|
17
|
+
*
|
|
18
|
+
* 依赖注入:
|
|
19
|
+
* - sseHub: SseHub 实例,用于实时推送进度
|
|
20
|
+
* - storage: createStorage() 返回的存储门面
|
|
21
|
+
* - flexRunnerFactory: 可选,自定义 FlexRunner 创建逻辑(测试注入 mock)
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* import { Orchestrator } from './Orchestrator/Orchestrator.js';
|
|
25
|
+
*
|
|
26
|
+
* const orch = new Orchestrator({ sseHub, storage });
|
|
27
|
+
* await orch.loadTask({
|
|
28
|
+
* taskId: 'task-001',
|
|
29
|
+
* taskDir: '/workspace/tasks/task-001',
|
|
30
|
+
* steps: [
|
|
31
|
+
* { stepId: 1, taskDescription: '分析代码', nextStepId: 2, maxRetries: 3 },
|
|
32
|
+
* { stepId: 2, dependId: 1, taskDescription: '生成报告', nextStepId: 0, maxRetries: 3 }
|
|
33
|
+
* ]
|
|
34
|
+
* });
|
|
35
|
+
* const result = await orch.start();
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import path from 'node:path';
|
|
39
|
+
|
|
40
|
+
import { FlexRunner } from '../FlexRunner/FlexRunner.js';
|
|
41
|
+
import { StepNode, NodeStatus } from './StepNode.js';
|
|
42
|
+
import { nowCST } from '../library/cstTime.js';
|
|
43
|
+
|
|
44
|
+
// ─── 常量 ────────────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
/** 编排器状态 */
|
|
47
|
+
export const OrchestratorStatus = Object.freeze({
|
|
48
|
+
IDLE: 'idle', // 未加载任务
|
|
49
|
+
LOADED: 'loaded', // 已加载,未启动
|
|
50
|
+
RUNNING: 'running', // 正在执行
|
|
51
|
+
COMPLETED: 'completed', // 全部成功完成
|
|
52
|
+
FAILED: 'failed', // 有步骤失败且无法继续
|
|
53
|
+
ABORTED: 'aborted', // 被手动终止
|
|
54
|
+
DEADLOCKED: 'deadlocked' // 死锁
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/** SSE 事件类型 */
|
|
58
|
+
const SSE_EVENT = Object.freeze({
|
|
59
|
+
TASK_START: 'orch:task:start',
|
|
60
|
+
STEP_STATUS: 'orch:step:status',
|
|
61
|
+
STEP_DETAIL: 'orch:step:detail',
|
|
62
|
+
TASK_PROGRESS: 'orch:task:progress',
|
|
63
|
+
TASK_COMPLETE: 'orch:task:complete',
|
|
64
|
+
TASK_ERROR: 'orch:task:error',
|
|
65
|
+
TASK_ABORTED: 'orch:task:aborted'
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/** 全局执行次数上限(防止极端循环场景) */
|
|
69
|
+
const GLOBAL_EXEC_LIMIT = 200;
|
|
70
|
+
|
|
71
|
+
/** 默认全局 Session 并发上限(可通过环境变量 MAX_CONCURRENT_SESSIONS 覆盖) */
|
|
72
|
+
const DEFAULT_MAX_CONCURRENT_SESSIONS = 50;
|
|
73
|
+
|
|
74
|
+
// ─── 全局 Session 并发信号量 ─────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Semaphore — 异步信号量(FIFO 排队)。
|
|
78
|
+
*
|
|
79
|
+
* 进程级单例(跨所有编排任务共享):并行步骤 fan-out / 多任务同时提交时,
|
|
80
|
+
* 活跃的 opencode Session(即正在执行的 FlexRunner executeStart)总数
|
|
81
|
+
* 不超过上限,防止 AI 会话数无节制叠加(5 维并发 × N 个仓库)。
|
|
82
|
+
*/
|
|
83
|
+
class Semaphore {
|
|
84
|
+
/**
|
|
85
|
+
* @param {number} limit - 并发上限(≥1)
|
|
86
|
+
*/
|
|
87
|
+
constructor(limit = DEFAULT_MAX_CONCURRENT_SESSIONS) {
|
|
88
|
+
this._limit = Math.max(1, Number(limit) || DEFAULT_MAX_CONCURRENT_SESSIONS);
|
|
89
|
+
this._active = 0;
|
|
90
|
+
/** @type {Array<() => void>} */
|
|
91
|
+
this._queue = [];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
get limit() { return this._limit; }
|
|
95
|
+
get active() { return this._active; }
|
|
96
|
+
get waiting() { return this._queue.length; }
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 获取一个槽位;上限已满时排队等待。
|
|
100
|
+
* @returns {Promise<void>}
|
|
101
|
+
*/
|
|
102
|
+
async acquire() {
|
|
103
|
+
if (this._active < this._limit) {
|
|
104
|
+
this._active++;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
await new Promise(resolve => this._queue.push(resolve));
|
|
108
|
+
// 被唤醒时槽位已由 release 直接转移给本等待者,_active 不再递增
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 释放槽位;有等待者时直接转移(保证 FIFO 且不超发)。
|
|
113
|
+
*/
|
|
114
|
+
release() {
|
|
115
|
+
const next = this._queue.shift();
|
|
116
|
+
if (next) {
|
|
117
|
+
next();
|
|
118
|
+
} else {
|
|
119
|
+
this._active = Math.max(0, this._active - 1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** 进程级全局 Session 并发信号量(所有 Orchestrator 实例共享) */
|
|
125
|
+
const globalSessionSemaphore = new Semaphore(
|
|
126
|
+
Number(process.env.MAX_CONCURRENT_SESSIONS) || DEFAULT_MAX_CONCURRENT_SESSIONS
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 查询全局 Session 并发占用快照(供 HTTP 统计接口 / 前端顶栏展示)。
|
|
131
|
+
* @returns {{active: number, limit: number, waiting: number, utilization: number}}
|
|
132
|
+
*/
|
|
133
|
+
export function getSessionSemaphoreStats() {
|
|
134
|
+
return {
|
|
135
|
+
active: globalSessionSemaphore.active,
|
|
136
|
+
limit: globalSessionSemaphore.limit,
|
|
137
|
+
waiting: globalSessionSemaphore.waiting,
|
|
138
|
+
utilization: globalSessionSemaphore.limit > 0
|
|
139
|
+
? Math.round((globalSessionSemaphore.active / globalSessionSemaphore.limit) * 100)
|
|
140
|
+
: 0
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ─── Orchestrator 类 ─────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
class Orchestrator {
|
|
147
|
+
/**
|
|
148
|
+
* @param {object} deps - 依赖注入
|
|
149
|
+
* @param {object} [deps.sseHub] - SseHub 实例(可选,无则不推送)
|
|
150
|
+
* @param {object} [deps.storage] - 存储门面(可选,无则不持久化)
|
|
151
|
+
* @param {function} [deps.flexRunnerFactory] - FlexRunner 工厂函数(可选,测试用)
|
|
152
|
+
* @param {object} [deps.sessionSemaphore] - Session 并发信号量(可选,默认进程级全局单例)
|
|
153
|
+
*/
|
|
154
|
+
constructor(deps = {}) {
|
|
155
|
+
this.sseHub = deps.sseHub || null;
|
|
156
|
+
this.storage = deps.storage || null;
|
|
157
|
+
this.flexRunnerFactory = deps.flexRunnerFactory || null;
|
|
158
|
+
// 全局 Session 并发限制(进程级共享;测试可注入自定义信号量)
|
|
159
|
+
this.sessionSemaphore = deps.sessionSemaphore || globalSessionSemaphore;
|
|
160
|
+
|
|
161
|
+
// ── 任务元信息 ──
|
|
162
|
+
/** @type {string} */
|
|
163
|
+
this._taskId = '';
|
|
164
|
+
/** @type {string} */
|
|
165
|
+
this._taskDir = '';
|
|
166
|
+
/** @type {string} */
|
|
167
|
+
this._skillDirectory = '';
|
|
168
|
+
/** @type {number} 大循环索引(0=首轮),用于区分各轮产物文件 */
|
|
169
|
+
this._loopCount = 0;
|
|
170
|
+
/** @type {OrchestratorStatus} */
|
|
171
|
+
this._status = OrchestratorStatus.IDLE;
|
|
172
|
+
/** @type {string|null} */
|
|
173
|
+
this._startTime = null;
|
|
174
|
+
/** @type {string|null} */
|
|
175
|
+
this._endTime = null;
|
|
176
|
+
|
|
177
|
+
// ── 步骤管理 ──
|
|
178
|
+
/** @type {Map<string, StepNode>} key = stepId */
|
|
179
|
+
this._nodes = new Map();
|
|
180
|
+
/** @type {string[]} 步骤执行顺序(拓扑排序后的入口步骤) */
|
|
181
|
+
this._entryStepIds = [];
|
|
182
|
+
|
|
183
|
+
// ── 运行时 ──
|
|
184
|
+
/** @type {string|null} 当前正在执行的 stepId(单步串行时);并行时为 null */
|
|
185
|
+
this._currentStepId = null;
|
|
186
|
+
/** @type {number} 全局执行计数 */
|
|
187
|
+
this._globalExecCount = 0;
|
|
188
|
+
/** @type {boolean} 是否被手动终止 */
|
|
189
|
+
this._aborted = false;
|
|
190
|
+
/** @type {string|null} 失败原因 */
|
|
191
|
+
this._error = null;
|
|
192
|
+
/** @type {Set<string>} 已参与过的执行路径节点(用于循环检测) */
|
|
193
|
+
this._execPathTrace = new Set();
|
|
194
|
+
/** @type {Set<string>} 修复步骤集合:被其它步骤 failStepId 指向的 stepId。
|
|
195
|
+
* 这些节点语义上"仅由某步骤失败时经 failStepId 进入",不应被 dependId fan-out 并行激活。 */
|
|
196
|
+
this._fixStepIds = new Set();
|
|
197
|
+
/** @type {Map<string, Promise<void>>} 在途执行追踪:key = stepId,value = _executeNode 的 Promise。
|
|
198
|
+
* 流水线式调度:步骤完成的尾部级联立即启动新就绪节点,
|
|
199
|
+
* _scheduleLoop 通过 Promise.race 感知任意节点完成,无需等整批结束。 */
|
|
200
|
+
this._inFlight = new Map();
|
|
201
|
+
|
|
202
|
+
// ── 持久化串行化 ──
|
|
203
|
+
// _persistState 可能被并发调用(如步骤状态变更回调中 fire-and-forget 触发)。
|
|
204
|
+
// 多个 writeFile 同时操作同一文件会导致截断/拼接竞态,产生损坏的 JSON。
|
|
205
|
+
// 用 Promise 链串行化所有持久化写入,后一次等待前一次完成后再执行。
|
|
206
|
+
/** @type {Promise<void>} */
|
|
207
|
+
this._persistChain = Promise.resolve();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ─── 只读 getter ───────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
/** @returns {string} */
|
|
213
|
+
get taskId() { return this._taskId; }
|
|
214
|
+
/** @returns {OrchestratorStatus} */
|
|
215
|
+
get status() { return this._status; }
|
|
216
|
+
/** @returns {string|null} */
|
|
217
|
+
get startTime() { return this._startTime; }
|
|
218
|
+
/** @returns {string|null} */
|
|
219
|
+
get endTime() { return this._endTime; }
|
|
220
|
+
/** @returns {StepNode[]} */
|
|
221
|
+
get nodes() { return Array.from(this._nodes.values()); }
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 获取步骤节点。
|
|
225
|
+
* @param {string|number} stepId
|
|
226
|
+
* @returns {StepNode|undefined}
|
|
227
|
+
*/
|
|
228
|
+
getNode(stepId) {
|
|
229
|
+
return this._nodes.get(String(stepId));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ─── 任务加载 ──────────────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 加载任务定义 JSON。
|
|
236
|
+
*
|
|
237
|
+
* 任务 JSON 格式:
|
|
238
|
+
* {
|
|
239
|
+
* taskId: 'task-001',
|
|
240
|
+
* taskDir: '/workspace/tasks/task-001',
|
|
241
|
+
* skillDirectory: '/path/to/Skills',
|
|
242
|
+
* steps: [
|
|
243
|
+
* {
|
|
244
|
+
* stepId: 1,
|
|
245
|
+
* taskDescription: '...',
|
|
246
|
+
* dependId: 0, // 0 = 无依赖(入口步骤)
|
|
247
|
+
* nextStepId: 2, // 成功后指向
|
|
248
|
+
* failStepId: 3, // 失败后指向(0 = 失败即终止)
|
|
249
|
+
* maxRetries: 3,
|
|
250
|
+
* skillDirectory: '...', // 可选,覆盖全局
|
|
251
|
+
* specifiedSkill: '...', // 可选,指定 SKILL(name 或目录名),跳过自动匹配
|
|
252
|
+
* taskDir: '...', // 可选,覆盖全局
|
|
253
|
+
* inputReport: '...' // 可选,初始输入报告
|
|
254
|
+
* },
|
|
255
|
+
* ...
|
|
256
|
+
* ]
|
|
257
|
+
* }
|
|
258
|
+
*
|
|
259
|
+
* @param {object} taskDef - 任务定义
|
|
260
|
+
* @returns {Promise<void>}
|
|
261
|
+
*/
|
|
262
|
+
async loadTask(taskDef) {
|
|
263
|
+
if (!taskDef || !taskDef.taskId) {
|
|
264
|
+
throw new Error('Orchestrator.loadTask: taskId 为必填项');
|
|
265
|
+
}
|
|
266
|
+
if (!Array.isArray(taskDef.steps) || taskDef.steps.length === 0) {
|
|
267
|
+
throw new Error('Orchestrator.loadTask: steps 为必填项且不能为空');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
this._taskId = String(taskDef.taskId);
|
|
271
|
+
// taskDir 默认为 db/task/{taskId}(与 storage.json('task') 同目录)
|
|
272
|
+
const _rootDir = this.storage?.manager?.rootDir;
|
|
273
|
+
this._taskDir = taskDef.taskDir ||
|
|
274
|
+
(_rootDir
|
|
275
|
+
? path.resolve(_rootDir, 'task', this._taskId)
|
|
276
|
+
: path.resolve(process.cwd(), 'db', 'task', this._taskId));
|
|
277
|
+
this._skillDirectory = taskDef.skillDirectory || '';
|
|
278
|
+
// 大循环索引(0=首轮),从 taskDef 读取,用于区分各轮产物文件
|
|
279
|
+
this._loopCount = Number(taskDef.loopCount) || 0;
|
|
280
|
+
this._nodes.clear();
|
|
281
|
+
this._entryStepIds = [];
|
|
282
|
+
|
|
283
|
+
// 构建步骤节点
|
|
284
|
+
for (const stepDef of taskDef.steps) {
|
|
285
|
+
const node = new StepNode({
|
|
286
|
+
...stepDef,
|
|
287
|
+
skillDirectory: stepDef.skillDirectory || this._skillDirectory,
|
|
288
|
+
taskDir: stepDef.taskDir || this._taskDir
|
|
289
|
+
});
|
|
290
|
+
if (this._nodes.has(node.stepId)) {
|
|
291
|
+
throw new Error(`Orchestrator.loadTask: stepId "${node.stepId}" 重复`);
|
|
292
|
+
}
|
|
293
|
+
this._nodes.set(node.stepId, node);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// 验证依赖引用完整性
|
|
297
|
+
this._validateReferences();
|
|
298
|
+
|
|
299
|
+
// 识别修复步骤集合:被其它步骤 failStepId 指向的 node。
|
|
300
|
+
// 这些 node 语义上"仅在某源步骤失败时经 failStepId 进入",不应通过 dependId
|
|
301
|
+
// 的 fan-out 并行激活(否则会与源步骤落入同一并行轮次,造成误执行/误阻塞)。
|
|
302
|
+
this._fixStepIds.clear();
|
|
303
|
+
for (const node of this._nodes.values()) {
|
|
304
|
+
const fail = String(node.failStepId);
|
|
305
|
+
if (fail !== '0' && this._nodes.has(fail)) {
|
|
306
|
+
this._fixStepIds.add(fail);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// 识别入口步骤(无任何前置依赖,或全部前置指向不存在的步骤)。
|
|
311
|
+
// 修复步骤(被某步骤 failStepId 指向的 node,见 _fixStepIds)默认不作为入口:
|
|
312
|
+
// 即使其 dependId 为 0/空/-1,也只应经 failStepId 触发,不能与真入口并行启动。
|
|
313
|
+
// 例外:重试锚点式回退(A.failStepId=B,B 恰为整条链的首个入口步骤,A 失败跳回 B 重跑全链)。
|
|
314
|
+
// 此时若排除修复步骤后找不到任何入口,则回退为不排除(保证这类图可启动)。
|
|
315
|
+
const collectEntries = (excludeFixSteps) => {
|
|
316
|
+
const ids = [];
|
|
317
|
+
for (const node of this._nodes.values()) {
|
|
318
|
+
if (excludeFixSteps && this._fixStepIds.has(node.stepId)) continue;
|
|
319
|
+
// 归一化后的前置依赖数组为空 → 入口步骤
|
|
320
|
+
const deps = node.dependencies;
|
|
321
|
+
const hasValidDep = deps.some(d => this._nodes.has(d));
|
|
322
|
+
if (deps.length === 0 || !hasValidDep) {
|
|
323
|
+
ids.push(node.stepId);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return ids;
|
|
327
|
+
};
|
|
328
|
+
this._entryStepIds = collectEntries(true);
|
|
329
|
+
if (this._entryStepIds.length === 0) {
|
|
330
|
+
// 排除修复步骤后无任何入口:可能是"重试锚点回退"(如 A.failStepId=B,B 为链首入口)。
|
|
331
|
+
// 回退为不排除修复步骤,让链首入口正常启动。
|
|
332
|
+
this._entryStepIds = collectEntries(false);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (this._entryStepIds.length === 0) {
|
|
336
|
+
throw new Error('Orchestrator.loadTask: 未找到入口步骤(dependId=0 的步骤)');
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
this._status = OrchestratorStatus.LOADED;
|
|
340
|
+
this._error = null;
|
|
341
|
+
this._aborted = false;
|
|
342
|
+
this._globalExecCount = 0;
|
|
343
|
+
this._execPathTrace.clear();
|
|
344
|
+
|
|
345
|
+
this._log('info', `任务已加载: taskId=${this._taskId}, 步骤数=${this._nodes.size}, 入口步骤=${this._entryStepIds.join(',')}`);
|
|
346
|
+
|
|
347
|
+
// 持久化初始状态
|
|
348
|
+
await this._persistState();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* 验证 nextStepId / failStepId / dependId 的引用完整性。
|
|
353
|
+
* 不要求所有引用都存在(failStepId=0 表示失败即终止),
|
|
354
|
+
* 但非零引用必须指向已定义的步骤。
|
|
355
|
+
* @private
|
|
356
|
+
*/
|
|
357
|
+
_validateReferences() {
|
|
358
|
+
for (const node of this._nodes.values()) {
|
|
359
|
+
// nextStepId 非 0 时必须存在
|
|
360
|
+
if (node.nextStepId !== '0' && !this._nodes.has(node.nextStepId)) {
|
|
361
|
+
throw new Error(
|
|
362
|
+
`Orchestrator.loadTask: 步骤 ${node.stepId} 的 nextStepId "${node.nextStepId}" 不存在`
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
// failStepId 非 0 时必须存在
|
|
366
|
+
if (node.failStepId !== '0' && !this._nodes.has(node.failStepId)) {
|
|
367
|
+
throw new Error(
|
|
368
|
+
`Orchestrator.loadTask: 步骤 ${node.stepId} 的 failStepId "${node.failStepId}" 不存在`
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
// dependId(单值或数组)中每个非 0 引用必须存在。
|
|
372
|
+
// 注意:-1 是"修复步骤哨兵"而非真实依赖 ID,StepNode 归一化时已从
|
|
373
|
+
// node.dependencies 排除,此处显式跳过以防御历史状态/手写任务中的 -1。
|
|
374
|
+
for (const depId of node.dependencies) {
|
|
375
|
+
if (depId === '-1') continue;
|
|
376
|
+
if (!this._nodes.has(depId)) {
|
|
377
|
+
this._log('warn', `步骤 ${node.stepId} 的 dependId "${depId}" 不存在,视为入口步骤`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ─── 执行控制 ──────────────────────────────────────────────────────
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* start — 启动编排执行。
|
|
387
|
+
*
|
|
388
|
+
* 从入口步骤开始,按依赖关系和 nextStepId/failStepId 链驱动执行。
|
|
389
|
+
* 无依赖的步骤并行执行;有依赖的步骤等待前置完成后触发。
|
|
390
|
+
*
|
|
391
|
+
* @returns {Promise<{status: OrchestratorStatus, summary: object}>}
|
|
392
|
+
*/
|
|
393
|
+
async start() {
|
|
394
|
+
if (this._status !== OrchestratorStatus.LOADED) {
|
|
395
|
+
throw new Error(`Orchestrator.start: 当前状态为 ${this._status},需先 loadTask`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// 启动前终止检查:load 与 start 之间的窗口期内收到 abort → 不执行任何步骤
|
|
399
|
+
if (this._aborted) {
|
|
400
|
+
this._status = OrchestratorStatus.ABORTED;
|
|
401
|
+
this._endTime = nowCST();
|
|
402
|
+
this._log('warn', 'start: 任务在启动前已被终止,不执行任何步骤');
|
|
403
|
+
this._emit(SSE_EVENT.TASK_ABORTED, this.getProgress());
|
|
404
|
+
await this._persistState();
|
|
405
|
+
return { status: this._status, summary: this.getProgress() };
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
this._status = OrchestratorStatus.RUNNING;
|
|
409
|
+
this._startTime = nowCST();
|
|
410
|
+
this._emit(SSE_EVENT.TASK_START, this.getProgress());
|
|
411
|
+
await this._persistState();
|
|
412
|
+
|
|
413
|
+
this._log('info', `── 编排执行开始 ──`);
|
|
414
|
+
|
|
415
|
+
try {
|
|
416
|
+
// 初始就绪:将所有入口步骤标记为 ready
|
|
417
|
+
for (const stepId of this._entryStepIds) {
|
|
418
|
+
const node = this._nodes.get(stepId);
|
|
419
|
+
if (node && node.status === NodeStatus.PENDING) {
|
|
420
|
+
node.markReady();
|
|
421
|
+
this._emitStepStatus(node);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// 主调度循环
|
|
426
|
+
await this._scheduleLoop();
|
|
427
|
+
|
|
428
|
+
} catch (err) {
|
|
429
|
+
this._error = err.message;
|
|
430
|
+
this._status = OrchestratorStatus.FAILED;
|
|
431
|
+
this._log('error', `编排执行异常: ${err.message}`);
|
|
432
|
+
this._emit(SSE_EVENT.TASK_ERROR, { error: err.message, progress: this.getProgress() });
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
this._endTime = nowCST();
|
|
436
|
+
|
|
437
|
+
// 判定最终状态
|
|
438
|
+
if (this._aborted) {
|
|
439
|
+
this._status = OrchestratorStatus.ABORTED;
|
|
440
|
+
this._emit(SSE_EVENT.TASK_ABORTED, this.getProgress());
|
|
441
|
+
} else if (this._status === OrchestratorStatus.RUNNING) {
|
|
442
|
+
// 检查是否全部成功完成
|
|
443
|
+
const allCompleted = Array.from(this._nodes.values()).every(n => n.isCompleted);
|
|
444
|
+
if (allCompleted) {
|
|
445
|
+
this._status = OrchestratorStatus.COMPLETED;
|
|
446
|
+
} else {
|
|
447
|
+
// 检查是否死锁
|
|
448
|
+
const hasRunning = Array.from(this._nodes.values()).some(n => n.isRunning);
|
|
449
|
+
const hasReady = Array.from(this._nodes.values()).some(n => n.status === NodeStatus.READY);
|
|
450
|
+
if (!hasRunning && !hasReady) {
|
|
451
|
+
this._status = OrchestratorStatus.DEADLOCKED;
|
|
452
|
+
this._error = '所有步骤已停止,但存在未完成的步骤(可能死锁或失败链终止)';
|
|
453
|
+
} else {
|
|
454
|
+
this._status = OrchestratorStatus.FAILED;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
this._emit(SSE_EVENT.TASK_COMPLETE, {
|
|
458
|
+
status: this._status,
|
|
459
|
+
progress: this.getProgress()
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
this._log('info', `── 编排执行结束: status=${this._status} ──`);
|
|
464
|
+
await this._persistState();
|
|
465
|
+
return { status: this._status, summary: this.getProgress() };
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* 释放编排器持有的重型引用(各个步骤的 FlexRunner 及其内嵌依赖)。
|
|
470
|
+
*
|
|
471
|
+
* 编排结束后由上层(OrchestratorController)调用,遍历所有节点置空 `_runner`,
|
|
472
|
+
* 使 FlexRunner / CLIController / SkillSelector Agent / LLMClient / Storage 可被 GC 回收。
|
|
473
|
+
*
|
|
474
|
+
* 仅释放内存引用,保留:
|
|
475
|
+
* - StepNode 元信息(status / sessionId / resultPath / history),供 toJSON 历史查询
|
|
476
|
+
* - 磁盘持久化文件(orchestration.json / loop.json / result .md)
|
|
477
|
+
*
|
|
478
|
+
* 调用后 getProgress / getStepInfo 仍可用;依赖 `_runner` 的 live/session 实时查询
|
|
479
|
+
* 将失效(前端走本地归档回退路径)。
|
|
480
|
+
*/
|
|
481
|
+
dispose() {
|
|
482
|
+
if (this._disposed) return;
|
|
483
|
+
for (const node of this._nodes.values()) {
|
|
484
|
+
node.releaseRunner();
|
|
485
|
+
}
|
|
486
|
+
this._disposed = true;
|
|
487
|
+
this._log('info', '── 编排器已释放重型引用 ──');
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* 主调度循环(流水线式事件驱动)。
|
|
492
|
+
*
|
|
493
|
+
* 与旧的"轮次式"(每轮收集一批 READY → Promise.all → 等整批完成)不同:
|
|
494
|
+
* 任一在途步骤完成时,其尾部级联已把新就绪的后续步骤投入执行(见 _executeNode 尾部 / _kick),
|
|
495
|
+
* 因此这里只需:
|
|
496
|
+
* 1. 首轮把全部 READY 节点 _kick
|
|
497
|
+
* 2. Promise.race 任意在途完成即重新扫描(此时尾部队列已注入新节点)
|
|
498
|
+
* 3. 无在途也无 READY → 调度结束
|
|
499
|
+
*
|
|
500
|
+
* 效果:步骤完成即刻启动依赖它的下一步骤(如步骤4 完成 → 步骤6 立即启动),
|
|
501
|
+
* 不再被同批次慢步骤(如步骤5)拖累;总耗时趋近 DAG 关键路径而非批次累计。
|
|
502
|
+
* fan-in 聚合、execPath 循环检测、GLOBAL_EXEC_LIMIT、abort 语义均不变。
|
|
503
|
+
*
|
|
504
|
+
* @private
|
|
505
|
+
*/
|
|
506
|
+
async _scheduleLoop() {
|
|
507
|
+
while (!this._aborted) {
|
|
508
|
+
// 全局执行次数保护
|
|
509
|
+
if (this._globalExecCount >= GLOBAL_EXEC_LIMIT) {
|
|
510
|
+
this._error = `全局执行次数已达上限 ${GLOBAL_EXEC_LIMIT},可能存在无限循环`;
|
|
511
|
+
this._log('error', this._error);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// 启动所有就绪节点
|
|
516
|
+
for (const node of this._nodes.values()) {
|
|
517
|
+
if (node.status === NodeStatus.READY) {
|
|
518
|
+
this._kick(node);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// 无可执行也无在途 → 调度结束
|
|
523
|
+
if (this._inFlight.size === 0) {
|
|
524
|
+
this._log('info', '调度循环结束:无可执行步骤');
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// 等待任意在途完成(其尾部级联会把新 READY 节点立刻变为在途),
|
|
529
|
+
// 完成后再回到循环顶部重新扫描。
|
|
530
|
+
await Promise.race(this._inFlight.values());
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* 启动一个就绪步骤(幂等,防重入)。
|
|
536
|
+
*
|
|
537
|
+
* - 仅当节点状态为 READY 且未超 GLOBAL_EXEC_LIMIT 时才真正执行;
|
|
538
|
+
* - 执行 Promise 记入 _inFlight(stepId → Promise),完成后移除;
|
|
539
|
+
* - _scheduleLoop 与 _executeNode 尾部级联均通过此方法启动节点,
|
|
540
|
+
* READY → RUNNING 状态转换保证同一节点不会被重复启动。
|
|
541
|
+
*
|
|
542
|
+
* @param {StepNode} node
|
|
543
|
+
* @private
|
|
544
|
+
*/
|
|
545
|
+
_kick(node) {
|
|
546
|
+
const stepId = node.stepId;
|
|
547
|
+
// 幂等:仅启动 READY 节点;RUNNING/PENDING/终态节点不重复入队
|
|
548
|
+
if (node.status !== NodeStatus.READY) return;
|
|
549
|
+
// 全局执行上限兜底(含尾部级联触发的启动)
|
|
550
|
+
if (this._globalExecCount >= GLOBAL_EXEC_LIMIT) return;
|
|
551
|
+
if (this._inFlight.has(stepId)) return;
|
|
552
|
+
|
|
553
|
+
const promise = this._executeNode(node);
|
|
554
|
+
this._inFlight.set(stepId, promise);
|
|
555
|
+
promise.finally(() => {
|
|
556
|
+
this._inFlight.delete(stepId);
|
|
557
|
+
}).catch(err => {
|
|
558
|
+
this._log('error', `步骤 ${stepId} 调度异常: ${err.message}`);
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* 执行单个步骤节点(单次执行,不再原地重试)。
|
|
564
|
+
*
|
|
565
|
+
* 流程:
|
|
566
|
+
* 1. 创建 FlexRunner 实例并 initContext
|
|
567
|
+
* 2. 调用 executeStart 单次执行
|
|
568
|
+
* 3. 成功 → 标记 COMPLETED,激活 nextStepId
|
|
569
|
+
* 4. 失败 → 已达 maxRetries 则终止流程;否则激活 failStepId(允许重复执行修复步骤)
|
|
570
|
+
* 5. 无 failStepId → 链终止
|
|
571
|
+
*
|
|
572
|
+
* @param {StepNode} node
|
|
573
|
+
* @private
|
|
574
|
+
*/
|
|
575
|
+
async _executeNode(node) {
|
|
576
|
+
// 调度前终止检查:终止已请求 → 不创建 FlexRunner、不启动 session,直接跳过
|
|
577
|
+
if (this._aborted) {
|
|
578
|
+
node.markSkipped('编排已终止');
|
|
579
|
+
this._emitStepStatus(node);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
this._globalExecCount++;
|
|
584
|
+
this._currentStepId = node.stepId;
|
|
585
|
+
|
|
586
|
+
// 获取前置步骤的结果报告路径(传递给当前步骤)
|
|
587
|
+
const inputReport = this._resolveInputReport(node);
|
|
588
|
+
|
|
589
|
+
// 标记为执行中(仅设置状态供 SSE 推送,retryCount 在 markRunning 中递增)
|
|
590
|
+
node.status = NodeStatus.RUNNING;
|
|
591
|
+
this._emitStepStatus(node);
|
|
592
|
+
|
|
593
|
+
let success = false;
|
|
594
|
+
let resultPath = '';
|
|
595
|
+
let sessionId = '';
|
|
596
|
+
let errorMsg = null;
|
|
597
|
+
let metrics = null;
|
|
598
|
+
|
|
599
|
+
try {
|
|
600
|
+
// 创建并初始化 FlexRunner
|
|
601
|
+
const runner = await this._createFlexRunner(node, inputReport);
|
|
602
|
+
node._runner = runner;
|
|
603
|
+
|
|
604
|
+
// Runner 创建期间收到终止请求 → 不执行(session 将不会被创建)
|
|
605
|
+
if (this._aborted) {
|
|
606
|
+
throw new Error('步骤被终止(Runner 初始化期间收到终止请求)');
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// 单次执行(不再原地重试)。
|
|
610
|
+
// 失败后由 _activateNextStep 跳转 failStepId(未达 maxRetries),
|
|
611
|
+
// 或终止流程(已达 maxRetries)。每次调用 markRunning() 递增 retryCount,
|
|
612
|
+
// 用于累计该步骤的总运行次数(maxRetries 上限判定)。
|
|
613
|
+
node.markRunning();
|
|
614
|
+
|
|
615
|
+
this._log('info', `执行步骤 ${node.stepId}(第 ${node.retryCount} 次)`);
|
|
616
|
+
|
|
617
|
+
// 步骤开始:实时落 SQLite(db/TaskStep/store.db)。
|
|
618
|
+
// running 记录先行写入,进程崩溃/重启时 db 中保留崩溃前真实状态。
|
|
619
|
+
await this._persistStepDb(node);
|
|
620
|
+
|
|
621
|
+
// ── 全局 Session 并发限制 ──
|
|
622
|
+
// 进程级信号量:并行 fan-out 步骤 / 多任务同时提交时,
|
|
623
|
+
// 活跃 opencode Session 总数不超过 MAX_CONCURRENT_SESSIONS(默认 5)
|
|
624
|
+
const semWaitStart = Date.now();
|
|
625
|
+
await this.sessionSemaphore.acquire();
|
|
626
|
+
const semWaited = Date.now() - semWaitStart;
|
|
627
|
+
if (semWaited > 100) {
|
|
628
|
+
this._log('info', `步骤 ${node.stepId} 等待并发槽位 ${semWaited}ms(active=${this.sessionSemaphore.active}/${this.sessionSemaphore.limit})`);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// 等待槽位期间收到终止请求 → 不启动 executeStart(session 将不会被创建)
|
|
632
|
+
if (this._aborted) {
|
|
633
|
+
this.sessionSemaphore.release();
|
|
634
|
+
this._log('warn', `步骤 ${node.stepId} 在等待并发槽位时被终止,跳过执行`);
|
|
635
|
+
throw new Error('步骤被终止(等待并发槽位期间收到终止请求)');
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
let result;
|
|
639
|
+
try {
|
|
640
|
+
result = await runner.executeStart();
|
|
641
|
+
} finally {
|
|
642
|
+
this.sessionSemaphore.release();
|
|
643
|
+
}
|
|
644
|
+
resultPath = result.resultPath;
|
|
645
|
+
success = result.success;
|
|
646
|
+
metrics = result.metrics || null;
|
|
647
|
+
|
|
648
|
+
// 推送执行细节
|
|
649
|
+
this._emit(SSE_EVENT.STEP_DETAIL, {
|
|
650
|
+
stepId: node.stepId,
|
|
651
|
+
attempt: node.retryCount,
|
|
652
|
+
success,
|
|
653
|
+
resultPath,
|
|
654
|
+
sessionId: runner.sessionId,
|
|
655
|
+
runner: this._runnerBrief(runner)
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
sessionId = runner.sessionId;
|
|
659
|
+
} catch (err) {
|
|
660
|
+
errorMsg = err.message;
|
|
661
|
+
this._log('error', `步骤 ${node.stepId} 执行异常: ${err.message}`);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// 标记完成状态
|
|
665
|
+
node.markFinished({ success, resultPath, sessionId, error: errorMsg, metrics });
|
|
666
|
+
this._emitStepStatus(node);
|
|
667
|
+
|
|
668
|
+
// 步骤终态:实时落 SQLite(completed/error + 输出/metrics/耗时)
|
|
669
|
+
await this._persistStepDb(node);
|
|
670
|
+
|
|
671
|
+
// 已被终止:整条链终止,不再激活后续步骤(nextStepId / failStepId 均不流转)
|
|
672
|
+
if (this._aborted) {
|
|
673
|
+
this._currentStepId = null;
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// #region agent log
|
|
678
|
+
fetch('http://127.0.0.1:7594/ingest/461ba96d-dcca-4412-802c-154cfd42eb29',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'0305de'},body:JSON.stringify({sessionId:'0305de',runId:'debug',location:'Orchestrator.js:468',message:'_executeNode finished',data:{node:node.stepId,success,retryCount:node.retryCount,maxRetries:node.maxRetries,nextStepId:node.nextStepId,failStepId:node.failStepId,status:node.status},timestamp:Date.now()})}).catch(()=>{});
|
|
679
|
+
// #endregion
|
|
680
|
+
|
|
681
|
+
if (success) {
|
|
682
|
+
// 成功 → 走 nextStepId
|
|
683
|
+
this._activateNextStep(node, node.nextStepId);
|
|
684
|
+
} else {
|
|
685
|
+
// 失败 → 已达本步骤自己的 maxRetries 上限则终止整个流程
|
|
686
|
+
if (node.retryCount >= node.maxRetries) {
|
|
687
|
+
this._log('warn',
|
|
688
|
+
`步骤 ${node.stepId} 失败且达到 maxRetries=${node.maxRetries},终止流程`);
|
|
689
|
+
} else if (node.failStepId !== '0') {
|
|
690
|
+
// 未达上限 → 立即跳 failStepId(允许重复执行已 completed/failed 修复步骤)。
|
|
691
|
+
// forceDependCheck:false 跳过目标步骤的 fan-in 依赖聚合检查——failStepId 是显式
|
|
692
|
+
// 失败跳转,不应被 dependId 聚合条件阻塞(修复目标失败即应进入修复步骤)。
|
|
693
|
+
this._activateNextStep(node, node.failStepId, { allowCompleted: true, forceDependCheck: false });
|
|
694
|
+
} else {
|
|
695
|
+
this._log('warn', `步骤 ${node.stepId} 失败且无 failStepId,链终止`);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// 本步骤完成,可能满足 fan-out/fan-in 依赖条件:扫描所有依赖它的后续步骤尝试激活
|
|
700
|
+
this._activateDependents(node);
|
|
701
|
+
|
|
702
|
+
// 级联启动:本步骤完成激活的新就绪节点应立即投入执行(流水线式),
|
|
703
|
+
// 无需等 _scheduleLoop 下一轮扫描(同一批次中其他慢步骤可能仍在运行)。
|
|
704
|
+
// 步骤4 完成 → 步骤6 即刻启动,不再被步骤5 拖累。
|
|
705
|
+
// 幂等由 _kick 的 READY→RUNNING 转换保证;abort 分支在尾部级联前已 return。
|
|
706
|
+
for (const candid of this._nodes.values()) {
|
|
707
|
+
if (candid.status === NodeStatus.READY) {
|
|
708
|
+
this._kick(candid);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
this._currentStepId = null;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* 激活后续步骤(标记为 READY),聚合感知。
|
|
717
|
+
*
|
|
718
|
+
* 成功/失败链语义(nextStepId/failStepId 单出边)保持不变,但激活前需满足
|
|
719
|
+
* 目标步骤自身的 fan-in 聚合条件:其所有前置依赖(node.dependencies)都须已 COMPLETED。
|
|
720
|
+
* 单依赖时行为与旧版完全一致;多依赖时需全部前置完成才真正 READY。
|
|
721
|
+
*
|
|
722
|
+
* @param {StepNode} fromNode - 触发方步骤
|
|
723
|
+
* @param {string} nextStepId - 下一步骤 ID
|
|
724
|
+
* @param {object} [opts]
|
|
725
|
+
* @param {boolean} [opts.allowCompleted=false] - failStepId 修复路径允许重复执行已完成/失败节点
|
|
726
|
+
* @param {boolean} [opts.forceDependCheck=true] - 是否检查目标节点的聚合条件(默认检查)
|
|
727
|
+
* @private
|
|
728
|
+
*/
|
|
729
|
+
_activateNextStep(fromNode, nextStepId, { allowCompleted = false, forceDependCheck = true } = {}) {
|
|
730
|
+
if (nextStepId === '0' || !this._nodes.has(nextStepId)) {
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const nextNode = this._nodes.get(nextStepId);
|
|
735
|
+
if (!nextNode) {
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// #region agent log
|
|
740
|
+
fetch('http://127.0.0.1:7594/ingest/461ba96d-dcca-4412-802c-154cfd42eb29',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'0305de'},body:JSON.stringify({sessionId:'0305de',runId:'debug',location:'Orchestrator.js:504',message:'_activateNextStep entry',data:{from:fromNode.stepId,next:nextStepId,allowCompleted,forceDependCheck,nextStatus:nextNode.status,nextIsPending:nextNode.isPending},timestamp:Date.now()})}).catch(()=>{});
|
|
741
|
+
// #endregion
|
|
742
|
+
|
|
743
|
+
// nextStepId 成功路径:激活待执行节点(PENDING/READY),
|
|
744
|
+
// 或重试修复链中已 FAILED 但未用尽 maxRetries 的节点
|
|
745
|
+
// (如 failStepId 跳回修复后成功后,再次走 nextStepId 到原失败节点)。
|
|
746
|
+
if (!allowCompleted && !nextNode.isPending) {
|
|
747
|
+
const canReenterFromFailed =
|
|
748
|
+
nextNode.status === NodeStatus.FAILED &&
|
|
749
|
+
nextNode.retryCount < nextNode.maxRetries;
|
|
750
|
+
if (!canReenterFromFailed) {
|
|
751
|
+
// #region agent log
|
|
752
|
+
fetch('http://127.0.0.1:7594/ingest/461ba96d-dcca-4412-802c-154cfd42eb29',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'0305de'},body:JSON.stringify({sessionId:'0305de',runId:'post-fix',location:'Orchestrator.js:515',message:'BLOCKED: not pending and cannot reenter from failed',data:{from:fromNode.stepId,next:nextStepId,nextStatus:nextNode.status,nextIsPending:nextNode.isPending,retryCount:nextNode.retryCount,maxRetries:nextNode.maxRetries},timestamp:Date.now()})}).catch(()=>{});
|
|
753
|
+
// #endregion
|
|
754
|
+
// 已执行或运行中(且非失败可重试),不重复激活
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// failStepId 修复路径:允许重复执行已 completed/failed 节点,
|
|
760
|
+
// 但跳过正在运行/已就绪(避免重复入队)
|
|
761
|
+
if (allowCompleted && (nextNode.isRunning || nextNode.status === NodeStatus.READY)) {
|
|
762
|
+
// #region agent log
|
|
763
|
+
fetch('http://127.0.0.1:7594/ingest/461ba96d-dcca-4412-802c-154cfd42eb29',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'0305de'},body:JSON.stringify({sessionId:'0305de',runId:'debug',location:'Orchestrator.js:522',message:'H3 BLOCKED: allowCompleted target running/ready',data:{from:fromNode.stepId,next:nextStepId,nextStatus:nextNode.status},timestamp:Date.now()})}).catch(()=>{});
|
|
764
|
+
// #endregion
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// 聚合条件:目标节点的所有前置依赖必须已完成(fan-in)
|
|
769
|
+
if (forceDependCheck && !this._allDependenciesCompleted(nextNode)) {
|
|
770
|
+
// #region agent log
|
|
771
|
+
fetch('http://127.0.0.1:7594/ingest/461ba96d-dcca-4412-802c-154cfd42eb29',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'0305de'},body:JSON.stringify({sessionId:'0305de',runId:'debug',location:'Orchestrator.js:527',message:'H2 BLOCKED: deps not all completed',data:{from:fromNode.stepId,next:nextStepId,deps:nextNode.dependencies},timestamp:Date.now()})}).catch(()=>{});
|
|
772
|
+
// #endregion
|
|
773
|
+
this._log('info',
|
|
774
|
+
`步骤 ${fromNode.stepId} → 步骤 ${nextStepId} 尚有未完成的前置依赖,等待聚合`);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// 不清空历史结果;运行时状态由 markRunning/markFinished 自然更新
|
|
779
|
+
nextNode.markReady();
|
|
780
|
+
// 传递触发方步骤的结果报告路径(fan-in 聚合时取最后一个触发的前置)
|
|
781
|
+
if (fromNode.resultPath) {
|
|
782
|
+
nextNode.inputReport = fromNode.resultPath;
|
|
783
|
+
}
|
|
784
|
+
this._emitStepStatus(nextNode);
|
|
785
|
+
this._log('info', `步骤 ${fromNode.stepId} → 激活步骤 ${nextStepId}`);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* 当前步骤完成后,扫描所有以它为前置依赖的后续步骤,尝试激活满足条件的步骤。
|
|
790
|
+
*
|
|
791
|
+
* 同时覆盖两种场景:
|
|
792
|
+
* - fan-out 并行:多个步骤 dependId 指向当前步骤,全部激活(由 _scheduleLoop 并行执行)
|
|
793
|
+
* - fan-in 聚合:目标步骤 dependId 为数组,仅当它所有前置都 COMPLETED 才激活
|
|
794
|
+
* 与 _activateNextStep(沿 nextStepId/failStepId 链)互补;重复激活由 isPending/markReady 幂等拦截。
|
|
795
|
+
*
|
|
796
|
+
* @param {StepNode} fromNode - 刚完成的步骤
|
|
797
|
+
* @private
|
|
798
|
+
*/
|
|
799
|
+
_activateDependents(fromNode) {
|
|
800
|
+
for (const node of this._nodes.values()) {
|
|
801
|
+
if (node === fromNode) continue;
|
|
802
|
+
// 仅当目标步骤依赖当前步骤,且仍处于待执行状态时才尝试激活
|
|
803
|
+
if (!node.dependencies.includes(fromNode.stepId)) continue;
|
|
804
|
+
if (!node.isPending) continue;
|
|
805
|
+
// 关键修复:当前步骤成功时,跳过"以当前步骤为 failStepId 目标"的修复链节点。
|
|
806
|
+
// 即若 fromNode.failStepId == node.stepId,则 node 只在 fromNode 失败时才进入;
|
|
807
|
+
// _activateDependents 不应在 fromNode 成功时把它当"成功依赖边"激活。
|
|
808
|
+
// 失败路径由 _executeNode 用 allowCompleted:true 走 failStepId 激活,无需在此重复。
|
|
809
|
+
if (fromNode.isCompleted && String(fromNode.failStepId) === String(node.stepId)) {
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
// 修复步骤兜底:node 是被某步骤 failStepId 指向的修复步骤(存入 _fixStepIds),
|
|
813
|
+
// 且当前 fromNode 不是它的触发失败源(fromNode.failStepId !== node.stepId)。
|
|
814
|
+
// 此时 node 的 dependId 若指向 fromNode(如修复步骤误设与验证步骤并行的 dependId),
|
|
815
|
+
// 会被当作 fan-out 并行激活,导致与源步骤同轮并行、误执行或阻塞后续链。
|
|
816
|
+
// 修复步骤只应经真正的 failStepId 触发,故此处跳过 fan-out 激活。
|
|
817
|
+
if (this._fixStepIds.has(node.stepId) &&
|
|
818
|
+
String(fromNode.failStepId) !== String(node.stepId)) {
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
if (this._allDependenciesCompleted(node)) {
|
|
822
|
+
this._activateNextStep(fromNode, node.stepId, { forceDependCheck: false });
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* 检查步骤的所有前置依赖是否全部完成(fan-in 聚合成就条件)。
|
|
829
|
+
*
|
|
830
|
+
* 无依赖(入口)或依赖全部 COMPLETED 时返回 true。
|
|
831
|
+
*
|
|
832
|
+
* @param {StepNode} node
|
|
833
|
+
* @returns {boolean}
|
|
834
|
+
* @private
|
|
835
|
+
*/
|
|
836
|
+
_allDependenciesCompleted(node) {
|
|
837
|
+
const deps = node.dependencies;
|
|
838
|
+
if (deps.length === 0) return true;
|
|
839
|
+
return deps.every(depId => {
|
|
840
|
+
const depNode = this._nodes.get(depId);
|
|
841
|
+
return depNode && depNode.isCompleted;
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* 解析步骤的输入报告路径。
|
|
847
|
+
*
|
|
848
|
+
* 优先使用节点自身的 inputReport(可能已被前置步骤传递),
|
|
849
|
+
* 否则为空。
|
|
850
|
+
*
|
|
851
|
+
* @param {StepNode} node
|
|
852
|
+
* @returns {string}
|
|
853
|
+
* @private
|
|
854
|
+
*/
|
|
855
|
+
_resolveInputReport(node) {
|
|
856
|
+
return node.inputReport || '';
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* 创建 FlexRunner 实例并初始化。
|
|
861
|
+
*
|
|
862
|
+
* @param {StepNode} node
|
|
863
|
+
* @param {string} inputReport
|
|
864
|
+
* @returns {Promise<FlexRunner>}
|
|
865
|
+
* @private
|
|
866
|
+
*/
|
|
867
|
+
async _createFlexRunner(node, inputReport) {
|
|
868
|
+
// 支持注入工厂(测试用)
|
|
869
|
+
if (this.flexRunnerFactory) {
|
|
870
|
+
return this.flexRunnerFactory(node, inputReport);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const runner = new FlexRunner();
|
|
874
|
+
await runner.initContext({
|
|
875
|
+
taskId: this._taskId,
|
|
876
|
+
stepId: Number(node.stepId) || 0,
|
|
877
|
+
// dependId 支持数组:FlexRunner 端仅作为回退锚点语义,数组时取第一个依赖
|
|
878
|
+
dependId: Number(node.dependencies[0]) || 0,
|
|
879
|
+
nextStepId: Number(node.nextStepId) || 0,
|
|
880
|
+
failStepId: Number(node.failStepId) || 0,
|
|
881
|
+
taskDescription: node.taskDescription,
|
|
882
|
+
skillDirectory: node.skillDirectory,
|
|
883
|
+
specifiedSkill: node.specifiedSkill,
|
|
884
|
+
taskDir: node.taskDir,
|
|
885
|
+
inputReport,
|
|
886
|
+
maxRetries: node.maxRetries,
|
|
887
|
+
// 大循环索引,传递给 FlexRunner 用于区分各轮产物文件
|
|
888
|
+
bigLoopIndex: this._loopCount,
|
|
889
|
+
// Session 创建后实时同步到 StepNode 并推送 SSE,保证运行中前端能拿到 sessionId
|
|
890
|
+
onSessionCreated: (sessionId) => {
|
|
891
|
+
node.sessionId = sessionId || '';
|
|
892
|
+
this._emitStepStatus(node);
|
|
893
|
+
}
|
|
894
|
+
});
|
|
895
|
+
return runner;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// ─── 控制操作 ──────────────────────────────────────────────────────
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* abort — 终止编排执行。
|
|
902
|
+
*
|
|
903
|
+
* 标记终止标志,正在执行的步骤会在步骤边界检测后退出。
|
|
904
|
+
* LOADED 状态(已加载未启动)同样受理终止:防止 start 前的终止请求
|
|
905
|
+
* 被吞掉导致随后 start() 照常执行整个编排。
|
|
906
|
+
*
|
|
907
|
+
* @returns {Promise<boolean>}
|
|
908
|
+
*/
|
|
909
|
+
async abort() {
|
|
910
|
+
if (this._status !== OrchestratorStatus.RUNNING &&
|
|
911
|
+
this._status !== OrchestratorStatus.LOADED) {
|
|
912
|
+
this._log('warn', `abort: 当前状态为 ${this._status},无需终止`);
|
|
913
|
+
return false;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
this._aborted = true;
|
|
917
|
+
this._log('warn', '⏹ 编排执行已被终止');
|
|
918
|
+
|
|
919
|
+
// 终止当前运行中的步骤。
|
|
920
|
+
// 注意:这里只通知 FlexRunner 终止(设置 #aborted + abortSession),
|
|
921
|
+
// 不再直接调用 node.markFinished —— 由 _executeNode 统一负责 markFinished,
|
|
922
|
+
// 避免 abort 与重试循环各自写入导致同一 attempt 重复记录。
|
|
923
|
+
for (const node of this._nodes.values()) {
|
|
924
|
+
if (node.isRunning && node._runner) {
|
|
925
|
+
try {
|
|
926
|
+
await node._runner.controlExecution('abort');
|
|
927
|
+
} catch (err) {
|
|
928
|
+
this._log('warn', `终止步骤 ${node.stepId} 失败: ${err.message}`);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
return true;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* pause — 暂停编排执行。
|
|
938
|
+
*
|
|
939
|
+
* 暂停所有运行中的步骤(在步骤边界生效)。
|
|
940
|
+
*
|
|
941
|
+
* @returns {Promise<boolean>}
|
|
942
|
+
*/
|
|
943
|
+
async pause() {
|
|
944
|
+
if (this._status !== OrchestratorStatus.RUNNING) return false;
|
|
945
|
+
|
|
946
|
+
for (const node of this._nodes.values()) {
|
|
947
|
+
if (node.isRunning && node._runner) {
|
|
948
|
+
try {
|
|
949
|
+
await node._runner.controlExecution('pause');
|
|
950
|
+
} catch (err) {
|
|
951
|
+
this._log('warn', `暂停步骤 ${node.stepId} 失败: ${err.message}`);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
this._log('info', '⏸ 编排执行已暂停');
|
|
956
|
+
return true;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* resume — 恢复编排执行。
|
|
961
|
+
*
|
|
962
|
+
* @returns {Promise<boolean>}
|
|
963
|
+
*/
|
|
964
|
+
async resume() {
|
|
965
|
+
if (this._status !== OrchestratorStatus.RUNNING) return false;
|
|
966
|
+
|
|
967
|
+
for (const node of this._nodes.values()) {
|
|
968
|
+
if (node.isRunning && node._runner) {
|
|
969
|
+
try {
|
|
970
|
+
await node._runner.controlExecution('resume');
|
|
971
|
+
} catch (err) {
|
|
972
|
+
this._log('warn', `恢复步骤 ${node.stepId} 失败: ${err.message}`);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
this._log('info', '▶ 编排执行已恢复');
|
|
977
|
+
return true;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* updateStepConfig — 修改未执行步骤的配置。
|
|
982
|
+
*
|
|
983
|
+
* 仅 PENDING / READY 状态的步骤允许修改。
|
|
984
|
+
* 已执行或运行中的步骤拒绝修改。
|
|
985
|
+
*
|
|
986
|
+
* @param {string|number} stepId
|
|
987
|
+
* @param {object} patch - 要修改的字段
|
|
988
|
+
* @returns {boolean} 是否修改成功
|
|
989
|
+
*/
|
|
990
|
+
updateStepConfig(stepId, patch) {
|
|
991
|
+
const node = this.getNode(stepId);
|
|
992
|
+
if (!node) {
|
|
993
|
+
throw new Error(`updateStepConfig: 步骤 "${stepId}" 不存在`);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
const ok = node.updateConfig(patch);
|
|
997
|
+
if (ok) {
|
|
998
|
+
this._log('info', `步骤 ${stepId} 配置已修改`);
|
|
999
|
+
this._emitStepStatus(node);
|
|
1000
|
+
} else {
|
|
1001
|
+
this._log('warn', `步骤 ${stepId} 当前状态为 ${node.status},不允许修改配置`);
|
|
1002
|
+
}
|
|
1003
|
+
return ok;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* getStepDetail — 获取步骤的执行细节。
|
|
1008
|
+
*
|
|
1009
|
+
* 通过 FlexRunner 的接口获取实时执行信息:
|
|
1010
|
+
* - liveMessages: 当前会话的实时对话消息
|
|
1011
|
+
* - sessionContent: 会话归档内容
|
|
1012
|
+
* - runner: FlexRunner 实例状态
|
|
1013
|
+
*
|
|
1014
|
+
* @param {string|number} stepId
|
|
1015
|
+
* @param {string} [detailType='live'] - 'live' | 'session' | 'status'
|
|
1016
|
+
* @returns {Promise<object>}
|
|
1017
|
+
*/
|
|
1018
|
+
async getStepDetail(stepId, detailType = 'live') {
|
|
1019
|
+
const node = this.getNode(stepId);
|
|
1020
|
+
if (!node) {
|
|
1021
|
+
throw new Error(`getStepDetail: 步骤 "${stepId}" 不存在`);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
const result = {
|
|
1025
|
+
stepId: node.stepId,
|
|
1026
|
+
status: node.status,
|
|
1027
|
+
retryCount: node.retryCount,
|
|
1028
|
+
history: node.history
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
// 通过关联的 FlexRunner 实例获取实时数据
|
|
1032
|
+
if (node._runner && detailType === 'live') {
|
|
1033
|
+
try {
|
|
1034
|
+
result.liveMessages = await node._runner.getLiveMessages();
|
|
1035
|
+
} catch (err) {
|
|
1036
|
+
result.liveMessages = [];
|
|
1037
|
+
result.liveError = err.message;
|
|
1038
|
+
}
|
|
1039
|
+
} else if (node._runner && detailType === 'session') {
|
|
1040
|
+
try {
|
|
1041
|
+
result.sessionContent = await node._runner.getSessionContent();
|
|
1042
|
+
} catch (err) {
|
|
1043
|
+
result.sessionError = err.message;
|
|
1044
|
+
}
|
|
1045
|
+
} else if (node._runner && detailType === 'status') {
|
|
1046
|
+
result.runner = this._runnerBrief(node._runner);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
return result;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// ─── 进度查询 ──────────────────────────────────────────────────────
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* getProgress — 获取编排进度概览。
|
|
1056
|
+
*
|
|
1057
|
+
* 返回所有步骤的状态汇总,供前端展示:
|
|
1058
|
+
* - 总步骤数
|
|
1059
|
+
* - 各状态的数量统计
|
|
1060
|
+
* - 各步骤的详情
|
|
1061
|
+
*
|
|
1062
|
+
* @returns {object}
|
|
1063
|
+
*/
|
|
1064
|
+
getProgress() {
|
|
1065
|
+
const steps = Array.from(this._nodes.values()).map(n => n.toJSON());
|
|
1066
|
+
const stats = {
|
|
1067
|
+
total: steps.length,
|
|
1068
|
+
pending: 0,
|
|
1069
|
+
ready: 0,
|
|
1070
|
+
running: 0,
|
|
1071
|
+
completed: 0,
|
|
1072
|
+
failed: 0,
|
|
1073
|
+
skipped: 0,
|
|
1074
|
+
blocked: 0
|
|
1075
|
+
};
|
|
1076
|
+
for (const s of steps) {
|
|
1077
|
+
if (stats[s.status] !== undefined) stats[s.status]++;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
return {
|
|
1081
|
+
taskId: this._taskId,
|
|
1082
|
+
loopCount: this._loopCount,
|
|
1083
|
+
// 终止已请求但仍未收尾(RUNNING + _aborted)→ 投影为 aborting,
|
|
1084
|
+
// 供前端展示「任务中止中」过渡态(不改变内部状态机,收尾时仍按 ABORTED 判定)
|
|
1085
|
+
status: (this._aborted && this._status === OrchestratorStatus.RUNNING)
|
|
1086
|
+
? 'aborting'
|
|
1087
|
+
: this._status,
|
|
1088
|
+
startTime: this._startTime,
|
|
1089
|
+
endTime: this._endTime,
|
|
1090
|
+
error: this._error,
|
|
1091
|
+
currentStepId: this._currentStepId,
|
|
1092
|
+
globalExecCount: this._globalExecCount,
|
|
1093
|
+
stats,
|
|
1094
|
+
steps
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// ─── 内部工具 ──────────────────────────────────────────────────────
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* 提取 FlexRunner 的简要状态信息。
|
|
1102
|
+
* @param {FlexRunner} runner
|
|
1103
|
+
* @returns {object}
|
|
1104
|
+
* @private
|
|
1105
|
+
*/
|
|
1106
|
+
_runnerBrief(runner) {
|
|
1107
|
+
if (!runner) return null;
|
|
1108
|
+
return {
|
|
1109
|
+
taskId: runner.taskId,
|
|
1110
|
+
stepId: runner.stepId,
|
|
1111
|
+
bigLoopIndex: runner.bigLoopIndex,
|
|
1112
|
+
executeStep: runner.executeStep,
|
|
1113
|
+
retryTimes: runner.retryTimes,
|
|
1114
|
+
maxRetries: runner.maxRetries,
|
|
1115
|
+
sessionId: runner.sessionId,
|
|
1116
|
+
startTime: runner.startTime,
|
|
1117
|
+
endTime: runner.endTime,
|
|
1118
|
+
resultPath: runner.resultPath,
|
|
1119
|
+
metrics: runner.metrics,
|
|
1120
|
+
canRetry: runner.canRetry()
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
/**
|
|
1125
|
+
* 推送步骤状态变更事件。
|
|
1126
|
+
* @param {StepNode} node
|
|
1127
|
+
* @private
|
|
1128
|
+
*/
|
|
1129
|
+
_emitStepStatus(node) {
|
|
1130
|
+
this._emit(SSE_EVENT.STEP_STATUS, {
|
|
1131
|
+
stepId: node.stepId,
|
|
1132
|
+
status: node.status,
|
|
1133
|
+
retryCount: node.retryCount,
|
|
1134
|
+
startTime: node.startTime,
|
|
1135
|
+
endTime: node.endTime,
|
|
1136
|
+
resultPath: node.resultPath,
|
|
1137
|
+
sessionId: node.sessionId,
|
|
1138
|
+
error: node.error,
|
|
1139
|
+
metrics: node.metrics
|
|
1140
|
+
});
|
|
1141
|
+
// 同时推送整体进度
|
|
1142
|
+
this._emit(SSE_EVENT.TASK_PROGRESS, this.getProgress());
|
|
1143
|
+
// 持久化状态变更
|
|
1144
|
+
this._persistState().catch(err => {
|
|
1145
|
+
this._log('warn', `持久化状态失败: ${err.message}`);
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// ─── 持久化 ──────────────────────────────────────────────────────
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* 将单个步骤的执行状态实时写入 SQLite(TaskStepStore,db/TaskStep/store.db)。
|
|
1153
|
+
*
|
|
1154
|
+
* 记录粒度:每次执行尝试(attempt)一条记录,id = `{taskId}::L{loop}::S{stepId}::A{attempt}`。
|
|
1155
|
+
* - markRunning 后调用 → upsert running 记录(崩溃时 db 保留"执行中"真实状态)
|
|
1156
|
+
* - markFinished 后调用 → update 为终态(completed/error,自动维护 finished_at/duration_ms)
|
|
1157
|
+
*
|
|
1158
|
+
* 与 JSON 持久化(orchestration.json / stepid_*.json)互为双写:
|
|
1159
|
+
* SQLite 是崩溃安全(WAL)的结构化历史,服务重启后可直接查询;
|
|
1160
|
+
* 静默失败,不影响编排主流程。
|
|
1161
|
+
*
|
|
1162
|
+
* @param {StepNode} node
|
|
1163
|
+
* @returns {Promise<void>}
|
|
1164
|
+
* @private
|
|
1165
|
+
*/
|
|
1166
|
+
async _persistStepDb(node) {
|
|
1167
|
+
if (!this.storage || !this._taskId) return;
|
|
1168
|
+
try {
|
|
1169
|
+
const store = this.storage.taskStepStore();
|
|
1170
|
+
const attempt = Math.max(1, node.retryCount);
|
|
1171
|
+
const id = `${this._taskId}::L${this._loopCount}::S${node.stepId}::A${attempt}`;
|
|
1172
|
+
|
|
1173
|
+
const meta = {
|
|
1174
|
+
taskType: 'orchestrator',
|
|
1175
|
+
loopCount: this._loopCount,
|
|
1176
|
+
stepId: node.stepId,
|
|
1177
|
+
attempt,
|
|
1178
|
+
retryCount: node.retryCount,
|
|
1179
|
+
maxRetries: node.maxRetries,
|
|
1180
|
+
dependId: node.dependId,
|
|
1181
|
+
nextStepId: node.nextStepId,
|
|
1182
|
+
failStepId: node.failStepId,
|
|
1183
|
+
inputReport: node.inputReport || null,
|
|
1184
|
+
specifiedSkill: node.specifiedSkill || null,
|
|
1185
|
+
sessionId: node.sessionId || null,
|
|
1186
|
+
resultPath: node.resultPath || null,
|
|
1187
|
+
metrics: node.metrics || null
|
|
1188
|
+
};
|
|
1189
|
+
|
|
1190
|
+
// NodeStatus → TaskStepStore status 映射
|
|
1191
|
+
const status = node.status === NodeStatus.COMPLETED ? 'completed'
|
|
1192
|
+
: node.status === NodeStatus.FAILED ? 'error'
|
|
1193
|
+
: node.status === NodeStatus.RUNNING ? 'running'
|
|
1194
|
+
: node.status === NodeStatus.SKIPPED ? 'skipped'
|
|
1195
|
+
: node.status === NodeStatus.BLOCKED ? 'skipped'
|
|
1196
|
+
: 'pending';
|
|
1197
|
+
|
|
1198
|
+
const finished = node.status === NodeStatus.COMPLETED || node.status === NodeStatus.FAILED;
|
|
1199
|
+
const output = finished ? {
|
|
1200
|
+
resultPath: node.resultPath,
|
|
1201
|
+
sessionId: node.sessionId,
|
|
1202
|
+
metrics: node.metrics,
|
|
1203
|
+
history: node.history
|
|
1204
|
+
} : null;
|
|
1205
|
+
|
|
1206
|
+
const existing = await store.get(id);
|
|
1207
|
+
if (existing) {
|
|
1208
|
+
await store.update(id, { status, output, error: node.error, meta });
|
|
1209
|
+
} else {
|
|
1210
|
+
await store.create({
|
|
1211
|
+
id,
|
|
1212
|
+
taskId: this._taskId,
|
|
1213
|
+
name: (node.taskDescription || '').slice(0, 200),
|
|
1214
|
+
type: 'cli',
|
|
1215
|
+
status,
|
|
1216
|
+
startedAt: node.startTime || undefined,
|
|
1217
|
+
input: {
|
|
1218
|
+
taskDescription: node.taskDescription,
|
|
1219
|
+
taskDir: node.taskDir,
|
|
1220
|
+
skillDirectory: node.skillDirectory
|
|
1221
|
+
},
|
|
1222
|
+
output,
|
|
1223
|
+
error: node.error,
|
|
1224
|
+
meta
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
} catch (err) {
|
|
1228
|
+
this._log('warn', `_persistStepDb 失败: step=${node.stepId}, ${err.message}`);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
/**
|
|
1233
|
+
* 获取可序列化的编排状态快照。
|
|
1234
|
+
* @returns {object}
|
|
1235
|
+
*/
|
|
1236
|
+
getPersistableState() {
|
|
1237
|
+
return {
|
|
1238
|
+
taskId: this._taskId,
|
|
1239
|
+
taskDir: this._taskDir,
|
|
1240
|
+
skillDirectory: this._skillDirectory,
|
|
1241
|
+
loopCount: this._loopCount,
|
|
1242
|
+
status: this._status,
|
|
1243
|
+
startTime: this._startTime,
|
|
1244
|
+
endTime: this._endTime,
|
|
1245
|
+
error: this._error,
|
|
1246
|
+
currentStepId: this._currentStepId,
|
|
1247
|
+
globalExecCount: this._globalExecCount,
|
|
1248
|
+
entryStepIds: [...this._entryStepIds],
|
|
1249
|
+
steps: Array.from(this._nodes.values()).map(n => n.toJSON()),
|
|
1250
|
+
persistedAt: nowCST()
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
/**
|
|
1255
|
+
* 持久化当前编排状态到 db/task/{taskId}/orchestration_loop{L}.json
|
|
1256
|
+
* 同时写入最新快照 db/task/{taskId}/orchestration.json(兼容恢复逻辑)
|
|
1257
|
+
* @private
|
|
1258
|
+
*/
|
|
1259
|
+
async _persistState() {
|
|
1260
|
+
if (!this.storage || !this._taskId) return;
|
|
1261
|
+
// 串行化:将本次写入追加到 _persistChain 末尾,确保同一时刻只有一个 writeFile 在执行。
|
|
1262
|
+
// 防止并发写入同一文件时因 O_TRUNC 截断产生损坏的 JSON。
|
|
1263
|
+
const run = async () => {
|
|
1264
|
+
try {
|
|
1265
|
+
const state = this.getPersistableState();
|
|
1266
|
+
const json = this.storage.json('task');
|
|
1267
|
+
// 按大循环轮次独立保存(保留历史)
|
|
1268
|
+
await json.write(`${this._taskId}/orchestration_loop${this._loopCount}`, state);
|
|
1269
|
+
// 同时写入最新快照(兼容已有 load/restore 逻辑)
|
|
1270
|
+
await json.write(`${this._taskId}/orchestration`, state);
|
|
1271
|
+
} catch (err) {
|
|
1272
|
+
this._log('warn', `_persistState 失败: ${err.message}`);
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1275
|
+
// 错误吞掉,避免一条失败链阻断后续写入
|
|
1276
|
+
this._persistChain = this._persistChain.then(run, run);
|
|
1277
|
+
// 对外仍 await,使调用方(如 loadTask/start/restoreState 中的 await)能感知完成
|
|
1278
|
+
await this._persistChain;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
/**
|
|
1282
|
+
* 从持久化文件恢复编排状态。
|
|
1283
|
+
*
|
|
1284
|
+
* 恢复后编排器处于对应状态:
|
|
1285
|
+
* - RUNNING → 标记为 FAILED(执行中断,无法自动恢复运行时上下文)
|
|
1286
|
+
* - LOADED/COMPLETED/FAILED/ABORTED/DEADLOCKED → 原样恢复
|
|
1287
|
+
*
|
|
1288
|
+
* @param {object} state - getPersistableState() 返回的对象
|
|
1289
|
+
* @param {object} [opts]
|
|
1290
|
+
* @param {boolean} [opts.queryOnly=false] - 只读查询模式:RUNNING 状态原样保留(不标记
|
|
1291
|
+
* FAILED、不写 SQLite)。仅用于「查询另一进程正在执行的任务」的场景——此时任务并未
|
|
1292
|
+
* 中断,标记 FAILED 会让前端误报"检视失败"。真正的中断恢复(服务重启 restoreFromDisk)
|
|
1293
|
+
* 不要传此选项,保持 RUNNING→FAILED 语义。
|
|
1294
|
+
* @returns {Promise<void>}
|
|
1295
|
+
*/
|
|
1296
|
+
async restoreState(state, opts = {}) {
|
|
1297
|
+
if (!state || !state.taskId) {
|
|
1298
|
+
throw new Error('Orchestrator.restoreState: state.taskId 为必填项');
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
this._taskId = String(state.taskId);
|
|
1302
|
+
this._taskDir = state.taskDir || '';
|
|
1303
|
+
this._skillDirectory = state.skillDirectory || '';
|
|
1304
|
+
this._loopCount = Number(state.loopCount) || 0;
|
|
1305
|
+
this._startTime = state.startTime || null;
|
|
1306
|
+
this._endTime = state.endTime || null;
|
|
1307
|
+
this._error = state.error || null;
|
|
1308
|
+
this._currentStepId = state.currentStepId || null;
|
|
1309
|
+
this._globalExecCount = state.globalExecCount || 0;
|
|
1310
|
+
this._entryStepIds = Array.isArray(state.entryStepIds) ? [...state.entryStepIds] : [];
|
|
1311
|
+
this._execPathTrace.clear();
|
|
1312
|
+
this._aborted = false;
|
|
1313
|
+
|
|
1314
|
+
// 重建步骤节点
|
|
1315
|
+
this._nodes.clear();
|
|
1316
|
+
if (Array.isArray(state.steps)) {
|
|
1317
|
+
for (const s of state.steps) {
|
|
1318
|
+
const node = new StepNode({
|
|
1319
|
+
stepId: s.stepId,
|
|
1320
|
+
taskDescription: s.taskDescription,
|
|
1321
|
+
dependId: s.dependId,
|
|
1322
|
+
nextStepId: s.nextStepId,
|
|
1323
|
+
failStepId: s.failStepId,
|
|
1324
|
+
maxRetries: s.maxRetries,
|
|
1325
|
+
skillDirectory: s.skillDirectory,
|
|
1326
|
+
specifiedSkill: s.specifiedSkill,
|
|
1327
|
+
taskDir: s.taskDir,
|
|
1328
|
+
inputReport: s.inputReport
|
|
1329
|
+
});
|
|
1330
|
+
// 恢复运行时状态
|
|
1331
|
+
node.status = s.status || NodeStatus.PENDING;
|
|
1332
|
+
node.retryCount = s.retryCount || 0;
|
|
1333
|
+
node.startTime = s.startTime || null;
|
|
1334
|
+
node.endTime = s.endTime || null;
|
|
1335
|
+
node.resultPath = s.resultPath || '';
|
|
1336
|
+
node.sessionId = s.sessionId || '';
|
|
1337
|
+
node.error = s.error || null;
|
|
1338
|
+
node.metrics = s.metrics || null;
|
|
1339
|
+
if (Array.isArray(s.history)) {
|
|
1340
|
+
node.history = s.history;
|
|
1341
|
+
}
|
|
1342
|
+
this._nodes.set(node.stepId, node);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
// 重建修复步骤集合(_activateDependents 的 fan-out 兜底依赖此集合)
|
|
1347
|
+
this._fixStepIds.clear();
|
|
1348
|
+
for (const node of this._nodes.values()) {
|
|
1349
|
+
const fail = String(node.failStepId);
|
|
1350
|
+
if (fail !== '0' && this._nodes.has(fail)) {
|
|
1351
|
+
this._fixStepIds.add(fail);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
// 防御:老持久化状态的 entryStepIds 可能误含修复步骤,恢复时过滤掉
|
|
1355
|
+
this._entryStepIds = (this._entryStepIds || []).filter(id => !this._fixStepIds.has(String(id)));
|
|
1356
|
+
|
|
1357
|
+
// RUNNING 状态在重启后无法恢复执行上下文,标记为 FAILED。
|
|
1358
|
+
// 例外:queryOnly 只读查询(如 Web 进程查询 CLI 进程正在执行的任务)——
|
|
1359
|
+
// 任务并未中断,原样保留 running 状态,也不写 SQLite。
|
|
1360
|
+
if (state.status === OrchestratorStatus.RUNNING) {
|
|
1361
|
+
if (opts.queryOnly) {
|
|
1362
|
+
this._status = OrchestratorStatus.RUNNING;
|
|
1363
|
+
} else {
|
|
1364
|
+
this._status = OrchestratorStatus.FAILED;
|
|
1365
|
+
this._error = '服务重启导致执行中断';
|
|
1366
|
+
this._endTime = nowCST();
|
|
1367
|
+
// 将运行中的步骤标记为失败(内存态)
|
|
1368
|
+
const interruptedNodes = [];
|
|
1369
|
+
for (const node of this._nodes.values()) {
|
|
1370
|
+
if (node.isRunning) {
|
|
1371
|
+
node.markFinished({ success: false, resultPath: '', error: '服务重启中断' });
|
|
1372
|
+
interruptedNodes.push(node);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
// 注意:不再调用 _persistState() 覆盖 orchestration.json / orchestration_loop{N}.json,
|
|
1376
|
+
// 保留崩溃前的真实快照(running 状态、currentStepId 等)供历史追溯。
|
|
1377
|
+
// 中断事实改记 SQLite(TaskStepStore)—— running 记录更新为 error 是真实发生的执行结果;
|
|
1378
|
+
// 注册表终态由上层 restoreFromDisk 回写。
|
|
1379
|
+
for (const node of interruptedNodes) {
|
|
1380
|
+
await this._persistStepDb(node);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
} else {
|
|
1384
|
+
this._status = state.status || OrchestratorStatus.LOADED;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/**
|
|
1389
|
+
* 通过 SseHub 推送事件(若可用)。
|
|
1390
|
+
* @param {string} eventType
|
|
1391
|
+
* @param {object} data
|
|
1392
|
+
* @private
|
|
1393
|
+
*/
|
|
1394
|
+
_emit(eventType, data) {
|
|
1395
|
+
if (this.sseHub && typeof this.sseHub.emit === 'function') {
|
|
1396
|
+
this.sseHub.emit(this._taskId, eventType, data);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
/**
|
|
1401
|
+
* 统一日志输出。
|
|
1402
|
+
* @param {'info'|'warn'|'error'} level
|
|
1403
|
+
* @param {string} message
|
|
1404
|
+
* @private
|
|
1405
|
+
*/
|
|
1406
|
+
_log(level, message) {
|
|
1407
|
+
const prefix = `[Orchestrator:${this._taskId || 'idle'}]`;
|
|
1408
|
+
const ts = nowCST();
|
|
1409
|
+
switch (level) {
|
|
1410
|
+
case 'error':
|
|
1411
|
+
console.error(`${ts} ${prefix} ❌ ${message}`);
|
|
1412
|
+
break;
|
|
1413
|
+
case 'warn':
|
|
1414
|
+
console.warn(`${ts} ${prefix} ⚠ ${message}`);
|
|
1415
|
+
break;
|
|
1416
|
+
default:
|
|
1417
|
+
console.info(`${ts} ${prefix} ℹ ${message}`);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
export { Orchestrator, SSE_EVENT };
|
|
1423
|
+
export { default as StepNode } from './StepNode.js';
|
|
1424
|
+
export { NodeStatus } from './StepNode.js';
|
|
1425
|
+
export default Orchestrator;
|