ソースを参照

feat(pipeline): 九任务串行总控 run_pipeline

wuzilong 1 ヶ月 前
親
コミット
81d9bcb5f9

+ 5 - 0
pipeline_bot/pipeline/ctx.py

@@ -66,6 +66,11 @@ class ChecklistWriter:
         _ = tmp.write_text("".join(lines), encoding="utf-8")
         _ = tmp.replace(self._path)
 
+    @property
+    def path(self) -> Path:
+        """打勾文件路径(只读暴露)。"""
+        return self._path
+
     def note(self, text: str) -> None:
         """追加一条备注行(重写时保留)。"""
         line = f"> {time.strftime(_FMT)} {text}\n"

+ 110 - 0
pipeline_bot/pipeline/run_pipeline.py

@@ -0,0 +1,110 @@
+"""run_pipeline — 九任务串行总控(一次登录, 全程共用会话)。
+
+用法:
+    cd pipeline_bot
+    uv run python pipeline/run_pipeline.py \
+        [-u longwu988] [-p Longwu1988] [--woma-minutes 30] \
+        [--zone 0] [--role-name ""] [--login-port 0] [--dry-run]
+"""
+from __future__ import annotations
+
+import argparse
+import importlib
+import logging
+import sys
+import time
+from pathlib import Path
+
+# 脚本方式运行(python pipeline/run_pipeline.py)时 sys.path[0]=pipeline/,
+# 项目根不入 path 则 `pipeline` 包与任务模块不可导入(uv 只安装 src/pbot)
+_ROOT = str(Path(__file__).resolve().parents[1])
+if _ROOT not in sys.path:
+    sys.path.insert(0, _ROOT)
+
+from pipeline.ctx import TaskResult  # noqa: E402
+
+TASKS: list[str] = [
+    "pipeline.tasks.t01_heal",
+    "pipeline.tasks.t02_biqi_kill20",
+    "pipeline.tasks.t03_cave_kill20",
+    "pipeline.tasks.t04_mine_kill20",
+    "pipeline.tasks.t05_wugong_kill40",
+    "pipeline.tasks.t06_shentong",
+    "pipeline.tasks.t07_wuxing",
+    "pipeline.tasks.t08_woma_farm",
+]
+
+# 失败后可继续的(软失败): T6 神通(等级信号弱判定) — 其余硬失败即停
+_SOFT: set[str] = {"pipeline.tasks.t06_shentong"}
+
+
+def parse_args() -> argparse.Namespace:
+    """解析 CLI 参数。"""
+    ap = argparse.ArgumentParser(prog="run_pipeline")
+    _ = ap.add_argument("-u", "--user", default="longwu988")
+    _ = ap.add_argument("-p", "--password", default="Longwu1988")
+    _ = ap.add_argument("--woma-minutes", type=float, default=30.0)
+    _ = ap.add_argument("--zone", type=int, default=0, help="0=最新区")
+    _ = ap.add_argument("--role-name", default="", help="空=随机中文名")
+    _ = ap.add_argument("--login-port", type=int, default=0)
+    _ = ap.add_argument("--dry-run", action="store_true",
+                        help="只打印任务序列, 不登录")
+    return ap.parse_args()
+
+
+def main() -> int:
+    """dry-run 打印任务序列; 正式跑登录一次串行执行, 软失败继续硬失败停。"""
+    a = parse_args()
+    logging.basicConfig(
+        level=logging.INFO,
+        format="%(asctime)s %(levelname)-7s %(name)s: %(message)s",
+        stream=sys.stdout,
+    )
+    log = logging.getLogger("pipeline")
+
+    if a.dry_run:
+        print("[dry-run] 任务序列:")  # noqa: T201 — dry-run 冒烟输出为硬需求
+        for t in TASKS:
+            mod = importlib.import_module(t)
+            print(f"  - {getattr(mod, 'TASK_TITLE', t)}")  # noqa: T201
+        print(f"[dry-run] 参数: zone={a.zone} role={a.role_name or '<随机>'} "  # noqa: T201
+              f"woma={a.woma_minutes}min")
+        return 0
+
+    from pipeline.bootstrap import bootstrap_session
+    ctx, real_name = bootstrap_session(
+        user=a.user, password=a.password, woma_minutes=a.woma_minutes,
+        login_port=a.login_port, force_zone=a.zone,
+        role_name=a.role_name)
+    log.info("会话就绪: 区=%s 角色=%s", ctx.zone_label, real_name)
+
+    exit_code = 0
+    try:
+        for name in TASKS:
+            mod = importlib.import_module(name)
+            title = getattr(mod, "TASK_TITLE", name)
+            log.info("======== %s ========", title)
+            t0 = time.time()
+            try:
+                result: TaskResult = mod.run_task(ctx)
+            except Exception as e:  # noqa: BLE001 — 单任务异常不拖垮全链
+                result = TaskResult.fail(f"异常: {e!r}")
+            dt = time.time() - t0
+            ctx.checklist.mark(title, result.ok, result.detail)
+            log.info("[%s] %s (%.0fs)", "✓" if result.ok else "✗",
+                     result.detail, dt)
+            if not result.ok and name not in _SOFT:
+                log.error("硬失败 — 流水线终止")
+                exit_code = 1
+                break
+            if not result.ok:
+                log.warning("软失败 — 继续下一任务")
+    finally:
+        ctx.stop[0] = True
+        ctx.conn.close()
+        log.info("会话已关闭; 打勾文件: %s", ctx.checklist.path)
+    return exit_code
+
+
+if __name__ == "__main__":
+    sys.exit(main())

+ 7 - 0
pipeline_bot/pipeline/tasks/t05_wugong_kill40.py

@@ -1,6 +1,7 @@
 """T5 — 任务指引→蜈蚣洞穴杀怪 40; 若等级<35 继续杀到 35(用户决策)。"""
 from __future__ import annotations
 
+import importlib
 import sys
 import time
 from pathlib import Path
@@ -29,6 +30,12 @@ def run_task(ctx: PipelineContext) -> TaskResult:
 
     from pbot.bot.runner import pump, run
 
+    # 防御性复位: t08 会覆写同模块常量为沃玛参数; 重跑/乱序时先还原蜈蚣默认
+    wugong_mod = importlib.import_module("run_juece13_wugong_farthest")
+    wugong_mod.CAVE_MAP = WUGONG_MAP
+    wugong_mod._ACK_BODY = WUGONG_MAP  # noqa: SLF001 — 同模块常量复位
+    wugong_mod._TELEPORT_ACTS = ("@jiuxian_move_1,1,2", "@act_move,1")  # noqa: SLF001
+
     walls = _cave_walls()
     total = 0
     for rnd in range(1, _MAX_ROUNDS + 1):

+ 1 - 1
pipeline_bot/pipeline/tasks/t07_wuxing.py

@@ -83,7 +83,7 @@ def _wait_success(ctx: PipelineContext, timeout: float) -> str | None:
 
 
 def run_task(ctx: PipelineContext) -> TaskResult:
-    """CS3031→传送盟重→点NPC10033→激活→扫双成功信号(超时重发一次)。"""
+    """CS3031→传送盟重→点NPC10031→激活→扫双成功信号(超时重发一次)。"""
     # 1) 任务面板体 + 前往(服务端自动传送盟重土城)
     body = (_ASSETS / "cs3031_full.bin").read_bytes()
     _ = ctx.conn.send_frame(3031, 0, 0, 0, ctx.sid, body)