From e32a43982bb2a17ab9dbd9cbdb6179a780a68a06 Mon Sep 17 00:00:00 2001 From: Val-sss <154882199@qq.com> Date: Thu, 30 Apr 2026 03:52:33 +0800 Subject: [PATCH] fix: wire up 4 remaining walkthrough issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. TrajectoryCollector.get_by_expert() — method was missing, ab_tester called it on expert graduation. Added to trajectory_collector.py. 2. auto_decompose integrated into _run_task() — hard tasks now auto-split via Planner.auto_decompose() + _handle_multi_with_tasks() helper. 3. Pre-search (P1-B) wired into _run_task() — Gate needs_search=true triggers QueryGenerator + ddg_search before orchestrator.run(), results passed via pre_search_results parameter. 4. session_md.save_session() wired into REPL exit — SESSION.md now written on vram_watcher.stop() with recent task summaries. All 4 issues from WALKTHROUGH_REPORT.md resolved. 311 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) --- kaiwu/cli/main.py | 77 ++++++++++++++++++++++++++ kaiwu/flywheel/trajectory_collector.py | 7 +++ 2 files changed, 84 insertions(+) diff --git a/kaiwu/cli/main.py b/kaiwu/cli/main.py index b964e24..962f83a 100644 --- a/kaiwu/cli/main.py +++ b/kaiwu/cli/main.py @@ -212,6 +212,29 @@ def _run_task(task, gate, orchestrator, memory, project_root, verbose, plan=Fals et = gate_result.get("expert_type", "chat") diff = gate_result.get("difficulty", "easy") + # ── P1-A: hard任务自动拆分 ── + _SKIP_DECOMPOSE_TYPES = {"chat", "office"} + if diff == "hard" and et not in _SKIP_DECOMPOSE_TYPES: + try: + from kaiwu.core.planner import Planner + from kaiwu.memory import pattern_md + planner = Planner( + locator=orchestrator.locator, + pattern_md_module=pattern_md, + llm=orchestrator.generator.llm, + ) + auto_tasks = planner.auto_decompose(task, gate_result, project_root) + if auto_tasks and len(auto_tasks) > 1: + console.print(f"\n [dim]检测到复杂任务,自动拆分为 {len(auto_tasks)} 个子任务[/dim]") + for t in auto_tasks: + dep = f" → 依赖{t['depends_on']}" if t["depends_on"] else "" + console.print(f" [dim] {t['id']}: {t['input'][:50]}{dep}[/dim]") + console.print() + _handle_multi_with_tasks(auto_tasks, gate, orchestrator, project_root, console) + return True + except Exception as e: + logger.warning("[main] 自动拆分异常: %s,走单任务", e) + # Plan mode (only for high-risk tasks) _SKIP_PLAN_TYPES = {"chat", "office"} should_plan = plan and et not in _SKIP_PLAN_TYPES @@ -258,12 +281,29 @@ def _run_task(task, gate, orchestrator, memory, project_root, verbose, plan=Fals progress.update(spin, description=_spinner_state["description"]) try: + # P1-B: 预搜索(Gate判断needs_search时) + pre_search = "" + if gate_result.get("needs_search") and not no_search and et not in ("chat", "office"): + try: + from kaiwu.search.query_generator import QueryGenerator + from kaiwu.search.duckduckgo import search as ddg_search + qg = QueryGenerator(llm=orchestrator.generator.llm) + search_query = qg.generate(task, intent="realtime") + if search_query: + results = ddg_search(search_query[0], max_results=3) + if results: + snippets = [f"[{r.get('title','')}] {r.get('snippet','')}" for r in results[:3] if r.get('snippet')] + pre_search = "\n".join(snippets)[:2000] + except Exception as e: + logger.debug("[main] 预搜索失败: %s", e) + result = orchestrator.run( user_input=task, gate_result=gate_result, project_root=project_root, on_status=_status_fn, no_search=no_search, + pre_search_results=pre_search, ) # 保存最后结果供conversation_history使用(问题7) orchestrator._last_result = result @@ -678,12 +718,49 @@ def _repl(model_path, ollama_url, ollama_model, project_root, verbose): # Cleanup vram_watcher.stop() + # P4: 会话连续性 — 保存SESSION.md + try: + from kaiwu.memory.session_md import save_session + tasks_done = [] + for msg in conversation_history: + if msg.get("role") == "assistant": + content = msg.get("content", "") + success = not content.startswith("[任务失败") + tasks_done.append({"input": content[:50], "success": success, "files": [], "elapsed": 0}) + if tasks_done: + save_session(project_root, tasks_done[-10:]) + except Exception: + pass + def _escape_html(text: str) -> str: """Escape HTML special chars for prompt_toolkit HTML.""" return text.replace("&", "&").replace("<", "<").replace(">", ">") +def _handle_multi_with_tasks(tasks, gate, orchestrator, project_root, console): + """复用TaskCompiler执行已解析的tasks(供auto_decompose调用)。""" + from kaiwu.core.task_compiler import TaskCompiler + from rich.progress import Progress, SpinnerColumn, TextColumn + + compiler = TaskCompiler(orchestrator=orchestrator, gate=gate, project_root=project_root) + + with Progress(SpinnerColumn(), TextColumn("{task.description}"), + console=console, transient=True) as progress: + ptask = progress.add_task("执行多任务...", total=None) + + def _on_status(stage, detail): + progress.update(ptask, description=detail[:60]) + + result = compiler.compile_and_run(tasks, on_status=_on_status) + + if result["success"]: + console.print(f" [bold green]✓ 完成[/bold green] ({result['elapsed']:.1f}s)") + else: + failed = [tid for tid, r in result["results"].items() if not r["success"]] + console.print(f" [bold yellow]部分完成[/bold yellow] — 失败: {', '.join(failed)}") + + # ── /multi command handler ─────────────────────────────────── def _handle_multi_command(arg: str, gate, orchestrator, project_root: str, console): diff --git a/kaiwu/flywheel/trajectory_collector.py b/kaiwu/flywheel/trajectory_collector.py index 2c13c3d..877cc65 100644 --- a/kaiwu/flywheel/trajectory_collector.py +++ b/kaiwu/flywheel/trajectory_collector.py @@ -124,3 +124,10 @@ class TrajectoryCollector: except Exception as e: logger.warning("Failed to load trajectory %s: %s", fname, e) return results + + def get_by_expert(self, expert_name: str) -> list[TaskTrajectory]: + """Load trajectories where the matched expert_name matches.""" + return [ + t for t in self._load_all() + if t.gate_result.get("expert_name") == expert_name + ]