126 lines
4.8 KiB
Python
126 lines
4.8 KiB
Python
|
|
"""Tool: block_resume – Recovery-Übersicht am Chat-Start (DB-backed)."""
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import json
|
|||
|
|
import os
|
|||
|
|
from typing import Any, Dict, List, Optional
|
|||
|
|
|
|||
|
|
from helpers.tool import Tool, Response
|
|||
|
|
from usr.plugins.a0_software_orchestrator.helpers.compact_protocol import get_resume_payload
|
|||
|
|
from usr.plugins.a0_software_orchestrator.helpers.db_state_store import (
|
|||
|
|
get_project_id,
|
|||
|
|
get_status, list_next_steps, list_todos, list_errors, list_worklog,
|
|||
|
|
get_kv, get_block_compact,
|
|||
|
|
)
|
|||
|
|
from usr.plugins.a0_software_orchestrator.helpers.exceptions import PluginDBError
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _resolve_name(project_name: str = "", project_root: str = "") -> str:
|
|||
|
|
if project_name:
|
|||
|
|
return project_name.strip()
|
|||
|
|
if project_root:
|
|||
|
|
return os.path.basename(project_root.rstrip("/"))
|
|||
|
|
return os.path.basename(os.getcwd())
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _build_status_line(artifacts_state: Dict[str, bool]) -> str:
|
|||
|
|
parts: List[str] = []
|
|||
|
|
for key, ok in artifacts_state.items():
|
|||
|
|
marker = "✅" if ok else "❌"
|
|||
|
|
parts.append(f"{key}={marker}")
|
|||
|
|
return ", ".join(parts) if parts else "(none)"
|
|||
|
|
|
|||
|
|
|
|||
|
|
class BlockResume(Tool):
|
|||
|
|
"""Lädt die kompakte 10-Zeilen-Resume-Übersicht aus der DB.
|
|||
|
|
|
|||
|
|
Args (kwargs):
|
|||
|
|
project_name (str): Projektname (bevorzugt).
|
|||
|
|
project_root (str): Legacy – wird zu basename() aufgelöst.
|
|||
|
|
include_full_resume (bool): Wenn True, zusätzlich resume_md ausgeben.
|
|||
|
|
include_conversation_summary (bool): Wenn True, zusätzlich conversation_summary_md.
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
async def execute(
|
|||
|
|
self,
|
|||
|
|
project_name: str = "",
|
|||
|
|
project_root: str = "",
|
|||
|
|
include_full_resume: bool = False,
|
|||
|
|
include_conversation_summary: bool = False,
|
|||
|
|
**kwargs,
|
|||
|
|
):
|
|||
|
|
name = _resolve_name(project_name, project_root)
|
|||
|
|
try:
|
|||
|
|
pid = get_project_id(name)
|
|||
|
|
if pid is None:
|
|||
|
|
raise LookupError(f"project '{name}' is not registered")
|
|||
|
|
except (ValueError, LookupError) as e:
|
|||
|
|
return Response(message=f"ERROR: {e}", break_loop=False)
|
|||
|
|
|
|||
|
|
except PluginDBError as e:
|
|||
|
|
return Response(message=f"Database error: {e}", break_loop=False)
|
|||
|
|
bc = get_block_compact(pid)
|
|||
|
|
status = get_status(pid)
|
|||
|
|
pending_steps = list_next_steps(pid, status="pending")
|
|||
|
|
open_todos = list_todos(pid, status="open")
|
|||
|
|
open_errors = list_errors(pid, status="open")
|
|||
|
|
recent_worklog = list_worklog(pid, limit=5)
|
|||
|
|
phase = get_kv(pid, "phase", default="<unknown>")
|
|||
|
|
|
|||
|
|
# 10-Zeilen-Übersicht
|
|||
|
|
lines: List[str] = [f"🔁 RESUME: {name} (pid={pid}) — phase={phase}"]
|
|||
|
|
if bc:
|
|||
|
|
lines.append(
|
|||
|
|
f" last block: {bc.get('last_block_id')} @ {bc.get('last_compact_at')}"
|
|||
|
|
)
|
|||
|
|
lines.append(f" next block: {bc.get('next_block') or '(none)'}")
|
|||
|
|
else:
|
|||
|
|
lines.append(" no block_compact marker yet")
|
|||
|
|
lines.append(
|
|||
|
|
f" status: {status or '(none)'}"
|
|||
|
|
)
|
|||
|
|
lines.append(
|
|||
|
|
f" pending next_steps: {len(pending_steps)}, "
|
|||
|
|
f"open todos: {len(open_todos)}, open errors: {len(open_errors)}"
|
|||
|
|
)
|
|||
|
|
if bc:
|
|||
|
|
decisions = bc.get("decisions") or []
|
|||
|
|
key_findings = bc.get("key_findings") or []
|
|||
|
|
open_questions = bc.get("open_questions") or []
|
|||
|
|
lines.append(
|
|||
|
|
f" last block decisions: {len(decisions)}, "
|
|||
|
|
f"key_findings: {len(key_findings)}, "
|
|||
|
|
f"open_questions: {len(open_questions)}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Optional: full resume + conversation summary
|
|||
|
|
if include_full_resume and bc and bc.get("resume_md"):
|
|||
|
|
lines.append("")
|
|||
|
|
lines.append("=== Full resume.md ===")
|
|||
|
|
lines.append(bc["resume_md"])
|
|||
|
|
|
|||
|
|
if include_conversation_summary and bc and bc.get("conversation_summary_md"):
|
|||
|
|
lines.append("")
|
|||
|
|
lines.append("=== Conversation summary ===")
|
|||
|
|
lines.append(bc["conversation_summary_md"])
|
|||
|
|
|
|||
|
|
# Recent worklog (top 5)
|
|||
|
|
if recent_worklog:
|
|||
|
|
lines.append("")
|
|||
|
|
lines.append("=== Recent worklog (top 5) ===")
|
|||
|
|
for w in recent_worklog:
|
|||
|
|
ts = w.get("created_at", "?")
|
|||
|
|
blk = w.get("work_block") or "-"
|
|||
|
|
agent = w.get("agent") or "-"
|
|||
|
|
summary = (w.get("summary") or "")[:100]
|
|||
|
|
lines.append(f" - [{ts}] {blk} ({agent}): {summary}")
|
|||
|
|
|
|||
|
|
# Hinweise für den Orchestrator
|
|||
|
|
lines.append("")
|
|||
|
|
lines.append("Hints:")
|
|||
|
|
lines.append(" - Don't re-run subagents for already-done blocks (see worklog).")
|
|||
|
|
lines.append(" - If pending_steps is empty, ask the user before continuing.")
|
|||
|
|
lines.append(" - If open_errors > 0, address them first.")
|
|||
|
|
|
|||
|
|
return Response(message="\n".join(lines), break_loop=False)
|