From 984d0320a7b6cab45bc3c084901372c1e5df78e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E5=AE=9D=E9=BE=99?= Date: Tue, 28 Jul 2026 16:09:45 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=8A=80=E8=83=BD=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fullstack-start-verify/agents/openai.yaml | 4 - .../start_and_verify.cpython-310.pyc | Bin 7891 -> 0 bytes .../database-connect/SKILL.md | 0 .../database-connect/agents/openai.yaml | 0 .../fullstack-start-verify/SKILL.md | 27 +++-- .../fullstack-start-verify/agents/openai.yaml | 4 + .../start_and_verify.cpython-310.pyc | Bin 0 -> 13346 bytes .../scripts/start_and_verify.py | 107 +++++++++++++++++- 8 files changed, 127 insertions(+), 15 deletions(-) delete mode 100644 .agents/skills/fullstack-start-verify/agents/openai.yaml delete mode 100644 .agents/skills/fullstack-start-verify/scripts/__pycache__/start_and_verify.cpython-310.pyc rename {.agents/skills => skills}/database-connect/SKILL.md (100%) rename {.agents/skills => skills}/database-connect/agents/openai.yaml (100%) rename {.agents/skills => skills}/fullstack-start-verify/SKILL.md (56%) create mode 100644 skills/fullstack-start-verify/agents/openai.yaml create mode 100644 skills/fullstack-start-verify/scripts/__pycache__/start_and_verify.cpython-310.pyc rename {.agents/skills => skills}/fullstack-start-verify/scripts/start_and_verify.py (75%) diff --git a/.agents/skills/fullstack-start-verify/agents/openai.yaml b/.agents/skills/fullstack-start-verify/agents/openai.yaml deleted file mode 100644 index b785fe5..0000000 --- a/.agents/skills/fullstack-start-verify/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "全栈启动验证" - short_description: "自动启动前后端服务并完成健康检查与端到端功能验证" - default_prompt: "使用 $fullstack-start-verify 启动项目的前后端,执行健康检查和端到端验证并汇报结果。" diff --git a/.agents/skills/fullstack-start-verify/scripts/__pycache__/start_and_verify.cpython-310.pyc b/.agents/skills/fullstack-start-verify/scripts/__pycache__/start_and_verify.cpython-310.pyc deleted file mode 100644 index c4cee00b8922eab0e9a84bde1337e8dfd4e49b29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7891 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 literal 0 HcmV?d00001 diff --git a/.agents/skills/fullstack-start-verify/scripts/start_and_verify.py b/skills/fullstack-start-verify/scripts/start_and_verify.py similarity index 75% rename from .agents/skills/fullstack-start-verify/scripts/start_and_verify.py rename to skills/fullstack-start-verify/scripts/start_and_verify.py index 4ce12f4..30428e6 100644 --- a/.agents/skills/fullstack-start-verify/scripts/start_and_verify.py +++ b/skills/fullstack-start-verify/scripts/start_and_verify.py @@ -27,6 +27,8 @@ class Service: cwd: Path url: str process: subprocess.Popen[bytes] | None = None + started_by_script: bool = False + reused: bool = False log_path: Path | None = None log_file: object | None = None @@ -73,6 +75,11 @@ def parse_args() -> argparse.Namespace: default=5.0, help="额外接口检查的单请求超时时间,默认 5 秒", ) + parser.add_argument( + "--reuse-running", + action="store_true", + help="健康地址已属于当前项目时复用已运行服务,不启动或清理该服务", + ) parser.add_argument( "--log-dir", type=Path, @@ -118,6 +125,7 @@ def start_service(service: Service, log_dir: Path) -> None: creationflags=creationflags, start_new_session=start_new_session, ) + service.started_by_script = True except Exception: service.log_file.close() service.log_file = None @@ -205,6 +213,87 @@ def wait_for_services( executor.shutdown(wait=not failed, cancel_futures=failed) +def reuse_running_services( + services: list[Service], timeout: float, run_id: str +) -> None: + """并行探测已运行服务,避免重复启动本项目服务。""" + if not services: + return + + probe_timeout = min(max(timeout, 0.5), 1.0) + + def probe(service: Service) -> tuple[Service, bool, str]: + ok, reason = request( + service.url, probe_timeout, f"{run_id}-{service.name}-reuse" + ) + return service, ok, reason + + with concurrent.futures.ThreadPoolExecutor(max_workers=len(services)) as executor: + results = list(executor.map(probe, services)) + for service, ok, reason in results: + if ok: + service.reused = True + print(f"复用 {service.name}:{service.url} [{reason}]") + + +def wait_for_services_and_checks( + services: list[Service], + checks: list[tuple[str, str]], + timeout: float, + interval: float, + check_timeout: float, + run_id: str, +) -> None: + """后端就绪后立即检查 API,同时继续等待前端,缩短总耗时。""" + if not services: + run_extra_checks(checks, check_timeout, run_id) + return + if not checks: + wait_for_services(services, timeout, interval, run_id) + return + + executor = concurrent.futures.ThreadPoolExecutor( + max_workers=len(services) + max(1, len(checks)) + ) + failed = True + try: + service_futures = [ + ( + service, + executor.submit(wait_for_service, service, timeout, interval, run_id), + ) + for service in services + ] + backend_future = next( + ( + future + for service, future in service_futures + if service.name == "backend" + ), + None, + ) + pending = {future for _, future in service_futures} + check_future: concurrent.futures.Future[None] | None = None + if backend_future is None: + check_future = executor.submit(run_extra_checks, checks, check_timeout, run_id) + pending.add(check_future) + + while pending: + done, pending = concurrent.futures.wait( + pending, return_when=concurrent.futures.FIRST_COMPLETED + ) + for future in done: + future.result() + if future is backend_future and check_future is None: + check_future = executor.submit( + run_extra_checks, checks, check_timeout, run_id + ) + pending.add(check_future) + failed = False + finally: + executor.shutdown(wait=not failed, cancel_futures=failed) + + def run_extra_checks( checks: list[tuple[str, str]], timeout: float, run_id: str ) -> None: @@ -228,7 +317,7 @@ def run_extra_checks( def stop_service(service: Service) -> None: process = service.process - if process is None or process.poll() is not None: + if not service.started_by_script or process is None or process.poll() is not None: return if os.name == "nt": @@ -298,10 +387,20 @@ def main() -> int: failure: Exception | None = None try: + if args.reuse_running: + reuse_running_services(services, args.check_timeout, run_id) for service in services: - start_service(service, log_dir) - wait_for_services(services, args.timeout, args.interval, run_id) - run_extra_checks(checks, args.check_timeout, run_id) + if not service.reused: + start_service(service, log_dir) + pending_services = [service for service in services if not service.reused] + wait_for_services_and_checks( + pending_services, + checks, + args.timeout, + args.interval, + args.check_timeout, + run_id, + ) print(f"全栈验证通过(FULLSTACK_VERIFY=PASS,任务 ID:{run_id})") return 0 except (OSError, RuntimeError, ValueError) as error: