feat(agent): hierarchical executor with path resolve, runtime probe, quiet UI
CI / build-gateway (push) Failing after 16s
CI / sync-config (push) Failing after 0s

Make Zed Agent closer to Cursor: deterministic DevOps path index, live Traefik
port probe before blind edits, stop-after-edit, and quieter Russian progress.
This commit is contained in:
2026-08-13 11:41:27 +03:00
parent 6fd2f0e689
commit a2d238d92e
71 changed files with 10617 additions and 449 deletions
+201
View File
@@ -0,0 +1,201 @@
"""Compact, readable progress lines for Zed agent chat."""
from __future__ import annotations
import re
from typing import Any
_MODEL_LABEL = {
"novita-planner": "Max",
"novita-verifier": "verify",
"a-medium-code": "Coder",
"a-simple": "Llama",
"b-complex": "DeepSeek",
"c-complex": "Max",
"approved-plan": "план",
}
_NOISE = (
"ждём LiteLLM",
"payload:",
"ctx ",
"ctx-",
"worker #",
"волна ",
"параллельно",
"bytes≈",
"upstream_stream",
"non-stream",
)
def model_label(model: str | None) -> str:
if not model:
return "модель"
m = str(model)
return _MODEL_LABEL.get(m, m.split("/")[-1][:18])
def short_path(path: str | None, *, max_parts: int = 3) -> str:
if not path:
return ""
p = str(path).replace("/", "\\").rstrip("\\")
parts = [x for x in p.split("\\") if x]
if len(parts) <= max_parts:
return "\\".join(parts)
return "\\".join(parts[-max_parts:])
def plan_paths(plan: dict[str, Any] | None) -> list[str]:
out: list[str] = []
for item in (plan or {}).get("subtasks") or []:
if not isinstance(item, dict):
continue
for p in item.get("paths") or []:
if isinstance(p, str) and p.strip() and p not in out:
out.append(p.strip())
return out
def is_noise(line: str) -> bool:
s = (line or "").strip()
if not s:
return True
low = s.lower()
return any(n.lower() in low for n in _NOISE)
def humanize_line(line: str) -> str | None:
"""Map internal progress → short Russian status. None = drop."""
s = (line or "").strip()
if not s or is_noise(s):
return None
low = s.lower()
if s in ("план…", "план...", "план"):
return "Планирую задачу"
if "план через" in low or low.startswith("hierarchical:"):
return "Планирую задачу"
if "утверждён" in low or "утвержден" in low:
m = re.search(r"(\d+)\s*подзадач", s)
n = m.group(1) if m else ""
return f"План утверждён{f' · {n} шаг(а)' if n else ''}"
if "отменён" in low or "отменен" in low:
return "План отменён"
if "правки от пользователя" in low or "пересборк" in low:
return "Пересобираю план по правкам"
if "ожидает утверждения" in low or "ждём утверждения" in low or "awaiting" in low:
return "План готов — подтверди («ok»)"
if "agent: план готов" in low or "executor с tools" in low:
return "Перехожу к выполнению"
if "готово за" in low:
return "Готово"
if "json битый" in low or "repair" in low:
return "Чиню формат плана"
if low.startswith("path_index") or "path_index hit" in low:
return None # shown via dedicated path line
if "path_resolve" in low or "поиск файл" in low:
return "Ищу файлы в проекте"
if "runtime_probe" in low or "runtime:" in low:
return "Проверяю runtime"
if "force edit" in low:
return "Правлю файл"
if "kickstart" in low:
return "Читаю файл"
if "escalate" in low or "mid-loop" in low:
return None
if "408" in s or "timeout" in low:
return "Таймаут модели — продолжаю иначе"
if "400" in s and "bad" in low:
return None
# strip technical prefixes
s = re.sub(r"^план:\s*", "", s, flags=re.I)
s = re.sub(r"^agent:\s*", "", s, flags=re.I)
if len(s) > 90:
s = s[:87] + ""
return s
def format_progress_block(lines: list[str]) -> str:
"""Final/static progress block — numbered, no junk."""
steps: list[str] = []
seen: set[str] = set()
for raw in lines or []:
h = humanize_line(str(raw))
if not h or h in seen:
continue
seen.add(h)
steps.append(h)
if not steps:
return ""
body = "\n".join(f"{i}. {t}" for i, t in enumerate(steps, 1))
return f"**Ход**\n{body}\n\n---\n\n"
def stream_header() -> str:
return "**Agent**\n"
def stream_step(text: str) -> str:
return f"· {text}\n"
def execution_banner(
*,
path_mode: bool,
plan: dict[str, Any] | None,
model: str | None,
) -> str:
"""Shown once after plan approve — what happens next."""
paths = plan_paths(plan)
lines = ["", "---", "", "**Выполнение**"]
facts = (plan or {}).get("runtime_facts") if isinstance(plan, dict) else None
step = 1
if isinstance(facts, dict) and (facts.get("host") or facts.get("hint")):
host = facts.get("host") or "?"
status = facts.get("http_status")
suggest = facts.get("suggested_backend_url")
bit = f"{host}"
if status is not None:
bit += f" → HTTP {status}"
if suggest:
bit += f" · fix `{suggest}`"
lines.append(f"{step}. Runtime: {bit}")
step += 1
if paths:
shown = ", ".join(f"`{short_path(p)}`" for p in paths[:2])
src = ""
tasks = (plan or {}).get("subtasks") or []
if tasks and isinstance(tasks[0], dict):
ps = str(tasks[0].get("path_source") or "")
if ps == "index":
src = " · индекс"
elif ps == "find":
src = " · поиск"
lines.append(f"{step}. Файл{src}: {shown}")
step += 1
if path_mode:
lines.append(f"{step}. Уточняю путь в репозитории…")
else:
lines.append(f"{step}. Читаю → правлю")
elif path_mode:
lines.append(f"{step}. Ищу нужные файлы…")
else:
lines.append(f"{step}. Запуск ({model_label(model)})")
lines.append("")
return "\n".join(lines) + "\n"
def tool_status(tool_names: list[str] | None, *, path: str | None = None) -> str:
names = [str(n).lower() for n in (tool_names or []) if n]
if any("edit" in n or "write" in n for n in names):
base = "Правлю"
elif any("read" in n for n in names):
base = "Читаю"
elif any("find" in n or "list" in n or "grep" in n for n in names):
base = "Ищу"
else:
base = "Инструмент"
if path:
return stream_step(f"{base} `{short_path(path)}`")
return stream_step(base)