iteration 8

This commit is contained in:
Christoph Schwörer
2026-08-28 19:41:13 +02:00
parent 37275c96d6
commit 8a22d586f1
182 changed files with 34254 additions and 18 deletions
+11 -3
View File
@@ -1025,8 +1025,13 @@ gelesen (`~/.cline/data/settings/providers.json`, Provider `tensorx`). Alternati
er per `--api-key` oder Umgebungsvariable `TENSORX_API_KEY` übergeben werden. Der Key
wird **nicht** in Laufartefakten gespeichert.
**Unterstützter Agentenmodus:** Nur `solo`. Der Adapter implementiert keine Subagenten;
`builtin` und `custom` sind nicht freigegeben und führen zum Abbruch.
**Unterstützter Agentenmodus:** `solo` (V1) und `builtin` (V1b). Der Modus wird per
`--mode solo|builtin` gesteuert. Im Modus `solo` steht das `spawn_subagent`-Tool nicht
zur Verfügung. Im Modus `builtin` kann der Hauptagent Subagenten mit eigenem Kontext
starten (`spawn_subagent`-Tool) – diese erhalten Read-Only-Tools (kein `write_file`) und
eine eigene, vom Hauptagenten unabhängige Konversation. Der Subagent-Typ (`explore` oder
`general-purpose`) bestimmt den System-Prompt. Subagent-Token fließen vollständig in
`usage` und `modelUsage` ein. `custom` ist nicht freigegeben und führt zum Abbruch.
**Isolation.** Der Adapter ist ein eigenständiges Python-Skript, das nur die
Python-Standardbibliothek und `requests` benötigt. Es liest die Codebasis über die
@@ -1067,7 +1072,8 @@ python "$skillDir\glm-kimi-adapter.py" `
--output "$lauf\Ergebnisse" `
--model $modell `
--effort $effort `
--max-turns 50 `
--mode $modus `
--max-turns 80 `
--result-dir $lauf `
2> "$lauf\Stderr.log"
@@ -1089,6 +1095,8 @@ Set-Content -Path "$lauf\_meta\endzeit.txt" -Value (Get-Date -Format o)
| Agent-Turns | `num_turns` |
| Tatsächlich eingesetztes Modell | `model` (aus API-Antwort; mit `model_requested` abzugleichen) |
| Tool-Aufrufe | `tool_call_count`, `tool_call_types` (nach Werkzeugname) |
| Subagenten | `subagent_stats` (`spawned`, `completed`, `failed`, `by_type`) – nur im Modus `builtin` |
| Subagenten-Details | `subagent_details` (je Subagent: Typ, Beschreibung, Turns, Tokens, Status) |
| Erzeugte Artefakte | `written_files` (Pfad und Größe je Datei) |
| Abschlusstext | `result` |
+359 -11
View File
@@ -206,8 +206,77 @@ TOOLS = [
},
},
},
{
"type": "function",
"function": {
"name": "spawn_subagent",
"description": (
"Starte einen Subagenten mit eigenem Kontext fuer eine isolierte Teilaufgabe. "
"Der Subagent kann Dateien lesen, Verzeichnisse auflisten, suchen und "
"Befehle ausfuehren – aber keine Ergebnisdateien schreiben. Verwende dies, "
"um einen Teil der Codebasis parallel oder isoliert zu analysieren. "
"Der Subagent erhaelt nur die Beschreibung, nicht den bisherigen "
"Konversationsverlauf. Gib eine praegnante Aufgabenbeschreibung."
),
"parameters": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Die Aufgabe fuer den Subagenten (z.B. 'Analysiere alle Berechtigungspruefungen in src/backend/Centron.BL/Security und erstelle eine Zusammenfassung der gefundenen Pruefungen mit Dateipfaden und Methodennamen')",
},
"subagent_type": {
"type": "string",
"description": "Typ des Subagenten: 'explore' fuer Code-Erkundung, 'general-purpose' fuer allgemeine Analyse",
},
},
"required": ["description"],
},
},
},
]
# Read-Only-Tools fuer Subagenten (kein write_file, kein spawn_subagent)
SUBAGENT_TOOLS = [t for t in TOOLS if t["function"]["name"] not in ("write_file", "spawn_subagent")]
# Standard-Subagent-Typen (fuer Modus 'builtin')
BUILTIN_SUBAGENT_PROMPTS = {
"explore": (
"Du bist ein Code-Explorations-Agent. Deine Aufgabe ist es, einen Teil der "
"Codebasis zu untersuchen und eine strukturierte Zusammenfassung deiner "
"Erkenntnisse zurueckzugeben. Nutze die Werkzeuge aktiv, um Dateien zu lesen "
"und zu durchsuchen. Gib am Ende eine kompakte Zusammenfassung mit konkreten "
"Dateipfaden, Klassennamen und Methodennamen zurueck."
),
"general-purpose": (
"Du bist ein Analyse-Agent. Du untersuchst die Codebasis und beantwortest "
"die dir gestellte Aufgabe. Nutze die Werkzeuge aktiv. Gib am Ende eine "
"praegnante Antwort mit konkreten Belegen (Dateipfade, Methodennamen) zurueck."
),
}
# Aktive Subagent-Prompts (wird je Modus gesetzt)
SUBAGENT_SYSTEM_PROMPTS = dict(BUILTIN_SUBAGENT_PROMPTS)
def load_custom_agents(agents_file):
"""
Laedt Agenten-Definitionen aus einer JSON-Datei fuer Modus 'custom'.
Format: { "agent_name": { "description": "...", "prompt": "..." }, ... }
Rueckgabe: dict agent_name -> system_prompt
"""
agents_path = Path(agents_file)
if not agents_path.is_file():
raise FileNotFoundError(f"Agenten-Datei nicht gefunden: {agents_file}")
data = json.loads(agents_path.read_text(encoding="utf-8"))
prompts = {}
for name, spec in data.items():
desc = spec.get("description", "")
prompt = spec.get("prompt", "")
prompts[name] = prompt
return prompts
# ---------------------------------------------------------------------------
# Pfad-Sicherheit
@@ -322,7 +391,8 @@ def tool_execute_command(root: str, args: dict) -> str:
return "ABGELEHNT: Befehl enthaelt verbotenes Muster. Schreibende und bauende Kommandos sind gesperrt."
try:
result = subprocess.run(
command, shell=True, cwd=root, capture_output=True, text=True, timeout=60,
command, shell=True, cwd=root, capture_output=True, text=True,
timeout=60, encoding="utf-8", errors="replace",
)
output = result.stdout or ""
if result.stderr:
@@ -341,6 +411,13 @@ def tool_write_file(output_dir: str, args: dict) -> str:
content = args.get("content", "")
if not path:
return "FEHLER: Kein Dateipfad angegeben"
# Modell gibt oft "Ergebnisse/<name>" als Pfad – Präfix entfernen
# da output_dir bereits das Ergebnisse-Verzeichnis ist.
path = path.replace("\\", "/")
for prefix in ("Ergebnisse/", "./Ergebnisse/", "ergebnisse/"):
if path.startswith(prefix):
path = path[len(prefix):]
break
try:
base = Path(output_dir).resolve()
target = (base / path).resolve()
@@ -368,6 +445,106 @@ def execute_tool(name: str, args: dict, root: str, output_dir: str) -> str:
return f"FEHLER: Unbekanntes Werkzeug: {name}"
# ---------------------------------------------------------------------------
# Subagent
# ---------------------------------------------------------------------------
def run_subagent(provider, model, api_key, effort, root, description,
subagent_type, temperature, timeout, max_turns=15):
"""
Startet einen Subagenten mit eigenem Kontext.
Der Subagent erhaelt Read-Only-Tools und eine begrenzte Turn-Anzahl.
Rueckgabe: dict mit result, usage, turns, tool_calls, status.
"""
sys_prompt = SUBAGENT_SYSTEM_PROMPTS.get(
subagent_type, SUBAGENT_SYSTEM_PROMPTS["general-purpose"]
)
messages = [
{"role": "system", "content": sys_prompt},
{"role": "user", "content": description},
]
sub_usage = {"prompt_tokens": 0, "completion_tokens": 0,
"total_tokens": 0, "cached_tokens": 0, "reasoning_tokens": 0}
sub_turns = 0
sub_tool_calls = 0
sub_result = ""
sub_errors = []
while sub_turns < max_turns:
sub_turns += 1
try:
resp = call_api_subagent(provider, model, messages, api_key,
effort, temperature, timeout)
except Exception as e:
sub_errors.append(str(e))
break
u = resp.get("usage", {})
sub_usage["prompt_tokens"] += u.get("prompt_tokens", 0)
sub_usage["completion_tokens"] += u.get("completion_tokens", 0)
sub_usage["total_tokens"] += u.get("total_tokens", 0)
sub_usage["cached_tokens"] += u.get("prompt_tokens_details", {}).get("cached_tokens", 0)
sub_usage["reasoning_tokens"] += u.get("completion_tokens_details", {}).get("reasoning_tokens", 0)
choices = resp.get("choices", [])
if not choices:
sub_errors.append("Keine choices in Subagent-Antwort")
break
msg = choices[0].get("message", {})
messages.append(msg)
content = msg.get("content", "")
if content:
sub_result = content
tool_calls = msg.get("tool_calls", [])
if not tool_calls:
break
for tc in tool_calls:
func = tc.get("function", {})
tname = func.get("name", "")
try:
targs = json.loads(func.get("arguments", "{}"))
except json.JSONDecodeError:
targs = {}
sub_tool_calls += 1
# Subagent darf nur Read-Only-Tools nutzen
if tname in ("read_file", "list_directory", "search_files", "execute_command"):
tresult = execute_tool(tname, targs, root, "")
else:
tresult = f"FEHLER: Werkzeug '{tname}' ist fuer Subagenten nicht freigegeben."
messages.append({"role": "tool", "tool_call_id": tc.get("id", ""),
"name": tname, "content": tresult})
return {
"result": sub_result or "(Subagent ohne Ergebnis)",
"usage": sub_usage,
"turns": sub_turns,
"tool_calls": sub_tool_calls,
"errors": sub_errors,
"status": "completed" if sub_result else "failed",
}
def call_api_subagent(provider, model, messages, api_key, effort, temperature, timeout):
"""API-Aufruf fuer Subagenten (mit SUBAGENT_TOOLS statt TOOLS)."""
url = f"{provider['base_url']}/chat/completions"
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
body = {
"model": model, "messages": messages, "tools": SUBAGENT_TOOLS,
"tool_choice": "auto", "temperature": temperature, "stream": False,
}
model_prefix = model.split("/")[0] if "/" in model else ""
effort_type = MODEL_EFFORT_TYPE.get(model_prefix, "thinking")
effort_val = EFFORT_MAP.get(effort, {}).get(effort_type, "medium")
if effort_type == "thinking":
body["thinking"] = {"type": "enabled", "level": effort_val}
elif effort_type == "reasoning_effort":
body["reasoning_effort"] = effort_val
resp = requests.post(url, headers=headers, json=body,
timeout=timeout if timeout > 0 else 1800)
if resp.status_code != 200:
raise RuntimeError(f"API-Fehler {resp.status_code}: {resp.text[:2000]}")
return resp.json()
# ---------------------------------------------------------------------------
# API-Aufruf
# ---------------------------------------------------------------------------
@@ -400,8 +577,35 @@ def call_api(provider, model, messages, api_key, effort, temperature, timeout):
# ---------------------------------------------------------------------------
def run_agent_loop(provider, model, system_prompt, user_prompt, api_key, effort,
root, output_dir, max_turns, temperature, timeout):
root, output_dir, max_turns, temperature, timeout, mode="solo"):
"""Fuehrt den Agent-Loop durch und sammelt Metriken."""
# Tools je nach Modus waehlen
if mode == "builtin":
active_tools = TOOLS # inklusive spawn_subagent
else:
active_tools = [t for t in TOOLS if t["function"]["name"] != "spawn_subagent"]
# call_api mit den aktiven Tools parametrisieren
def _call_api(messages):
url = f"{provider['base_url']}/chat/completions"
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
body = {
"model": model, "messages": messages, "tools": active_tools,
"tool_choice": "auto", "temperature": temperature, "stream": False,
}
model_prefix = model.split("/")[0] if "/" in model else ""
effort_type = MODEL_EFFORT_TYPE.get(model_prefix, "thinking")
effort_val = EFFORT_MAP.get(effort, {}).get(effort_type, "medium")
if effort_type == "thinking":
body["thinking"] = {"type": "enabled", "level": effort_val}
elif effort_type == "reasoning_effort":
body["reasoning_effort"] = effort_val
resp = requests.post(url, headers=headers, json=body,
timeout=timeout if timeout > 0 else 1800)
if resp.status_code != 200:
raise RuntimeError(f"API-Fehler {resp.status_code}: {resp.text[:2000]}")
return resp.json()
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
@@ -415,12 +619,51 @@ def run_agent_loop(provider, model, system_prompt, user_prompt, api_key, effort,
finish_reason = None
errors = []
start_time = time.time()
# Subagent-Tracking
subagent_stats = {"spawned": 0, "completed": 0, "failed": 0, "by_type": {}}
subagent_details = []
MAX_SUBAGENTS = 10 # Hartes Limit: danach wird spawn_subagent verweigert
write_file_count = 0 # Zaehlt write_file-Aufrufe
write_reminder_sent = False # Wurde schon eine Schreib-Erinnerung gesendet?
while turns < max_turns:
turns += 1
# Schreib-Erinnerung: Wenn nach 1/3 der Turns noch kein write_file,
# oder nach 2/3 der Turns weniger als 3 write_file-Aufrufe
if not write_reminder_sent and turns >= max_turns // 3 and write_file_count == 0:
messages.append({
"role": "user",
"content": (
"WICHTIG: Du hast bisher keine Ergebnisdateien geschrieben. "
"Beginne JETZT damit, deine Analyseergebnisse mit write_file "
"in die vorgegebenen Dateien (StRS.md, SyRS.md, SwRS.md, "
"Traceability.md, Hypothesen.md, Glossar.md, Analysebericht.md) "
"zu schreiben. Schreibe nicht weiter Subagenten — formalisiere "
"deine bisherigen Erkenntnisse in Anforderungen."
),
})
write_reminder_sent = True
sys.stderr.write(f"[glm-kimi-adapter] Schreib-Erinnerung gesendet "
f"(Turn {turns}, 0 write_file-Aufrufe)\n")
elif (not write_reminder_sent and turns >= max_turns * 2 // 3
and write_file_count < 3):
messages.append({
"role": "user",
"content": (
f"WICHTIG: Du hast bisher nur {write_file_count} Ergebnisdatei(en) "
f"geschrieben. Es fehlen noch mehrere der 7 vorgegebenen Dateien "
f"(StRS.md, SyRS.md, SwRS.md, Traceability.md, Hypothesen.md, "
f"Glossar.md, Analysebericht.md). Schreibe die fehlenden Dateien "
f"JETZT mit write_file."
),
})
write_reminder_sent = True
sys.stderr.write(f"[glm-kimi-adapter] Schreib-Erinnerung gesendet "
f"(Turn {turns}, {write_file_count} write_file-Aufrufe)\n")
try:
response = call_api(provider, model, messages, api_key,
effort, temperature, timeout)
response = _call_api(messages)
except Exception as e:
errors.append(f"Turn {turns}: API-Fehler: {e}")
break
@@ -430,7 +673,6 @@ def run_agent_loop(provider, model, system_prompt, user_prompt, api_key, effort,
total_usage["total_tokens"] += usage.get("total_tokens", 0)
cached = usage.get("prompt_tokens_details", {}).get("cached_tokens", 0)
total_usage["cached_tokens"] += cached
# Reasoning/Thinking-Tokens aus completion_tokens_details
comp_details = usage.get("completion_tokens_details", {})
total_usage["reasoning_tokens"] += comp_details.get("reasoning_tokens", 0)
if response.get("model"):
@@ -459,7 +701,60 @@ def run_agent_loop(provider, model, system_prompt, user_prompt, api_key, effort,
except json.JSONDecodeError:
tool_args = {}
tool_calls_log.append({"turn": turns, "name": tool_name, "args": tool_args})
result = execute_tool(tool_name, tool_args, root, output_dir)
# write_file-Zaehler erhoehen
if tool_name == "write_file":
write_file_count += 1
# Subagent-Spawning behandeln
if tool_name == "spawn_subagent":
if subagent_stats["spawned"] >= MAX_SUBAGENTS:
# Hartes Limit erreicht: verweigere und erzwinge Schreibphase
result = (
"ABGELEHNT: Subagent-Limit erreicht (10/10). "
"Du hast bereits 10 Subagenten gestartet. "
"Schreibe JETZT deine Ergebnisdateien mit write_file: "
"StRS.md, SyRS.md, SwRS.md, Traceability.md, "
"Hypothesen.md, Glossar.md, Analysebericht.md. "
"Starte keine weiteren Subagenten."
)
sys.stderr.write(f"[glm-kimi-adapter] Subagent verweigert "
f"(Limit {MAX_SUBAGENTS} erreicht)\n")
else:
sa_desc = tool_args.get("description", "")
sa_type = tool_args.get("subagent_type", "general-purpose")
subagent_stats["spawned"] += 1
subagent_stats["by_type"][sa_type] = subagent_stats["by_type"].get(sa_type, 0) + 1
sys.stderr.write(f"[glm-kimi-adapter] Subagent "
f"{subagent_stats['spawned']}/{MAX_SUBAGENTS} "
f"gestartet (Typ: {sa_type})\n")
try:
sa_result = run_subagent(
provider, model, api_key, effort, root,
sa_desc, sa_type, temperature, timeout
)
subagent_stats["completed" if sa_result["status"] == "completed" else "failed"] += 1
sa_u = sa_result["usage"]
total_usage["prompt_tokens"] += sa_u["prompt_tokens"]
total_usage["completion_tokens"] += sa_u["completion_tokens"]
total_usage["total_tokens"] += sa_u["total_tokens"]
total_usage["cached_tokens"] += sa_u["cached_tokens"]
total_usage["reasoning_tokens"] += sa_u["reasoning_tokens"]
subagent_details.append({
"id": subagent_stats["spawned"],
"type": sa_type,
"description": sa_desc[:200],
"turns": sa_result["turns"],
"tool_calls": sa_result["tool_calls"],
"tokens": sa_u["total_tokens"],
"status": sa_result["status"],
})
result = sa_result["result"]
except Exception as e:
subagent_stats["failed"] += 1
result = f"FEHLER: Subagent fehlgeschlagen: {e}"
else:
result = execute_tool(tool_name, tool_args, root, output_dir)
messages.append({"role": "tool", "tool_call_id": tc_id,
"name": tool_name, "content": result})
@@ -511,7 +806,10 @@ def run_agent_loop(provider, model, system_prompt, user_prompt, api_key, effort,
"tool_call_types": tool_call_types,
"written_files": written_files, "result": final_content,
"finish_reason": finish_reason, "errors": errors, "session_id": "",
"adapter": "python-glm-kimi", "adapter_version": "1.0.0",
"adapter": "python-glm-kimi", "adapter_version": "1.1.0",
"mode": mode,
"subagent_stats": subagent_stats,
"subagent_details": subagent_details,
}
@@ -528,6 +826,8 @@ def main():
parser.add_argument("--provider", default="tensorx", help="API-Provider (default: tensorx)")
parser.add_argument("--api-key", default=None, help="API-Key (default: aus Cline providers.json)")
parser.add_argument("--effort", default="high", choices=["low", "medium", "high", "xhigh", "max"])
parser.add_argument("--mode", default="solo", choices=["solo", "builtin", "custom"], help="Agentenmodus (solo=keine Subagenten, builtin=eingebaute, custom=vordefinierte Agenten aus Datei)")
parser.add_argument("--agents", default=None, help="Pfad zu Agenten-Definitionen (JSON) fuer Modus 'custom'")
parser.add_argument("--max-turns", type=int, default=50)
parser.add_argument("--temperature", type=float, default=1.0)
parser.add_argument("--timeout", type=int, default=0, help="Timeout in Sek (0=keins)")
@@ -567,8 +867,50 @@ def main():
"Du bist ein Requirements Engineer im Reverse Requirements Engineering "
"eines Legacy-ERP-Systems. Du analysierst die Codebasis im Arbeitsverzeichnis "
"und erstellst eine Anforderungsspezifikation nach ISO/IEC/IEEE 29148:2018.\n\n"
"Werkzeuge: read_file, list_directory, search_files, execute_command, write_file.\n"
"Die Codebasis wird ausschliesslich GELESEN. Schreibe Ergebnisdateien mit "
"Werkzeuge: read_file, list_directory, search_files, execute_command, write_file.\n\n"
"WICHTIG: Beginne deine Arbeit mit dem Schreiben von Ergebnisdateien (write_file), "
"nicht mit dem Erkunden. Erstelle zuerst die Skelette der 7 Ergebnisdateien "
"(StRS.md, SyRS.md, SwRS.md, Traceability.md, Hypothesen.md, Glossar.md, "
"Analysebericht.md) mit Platzhalter-Inhalt, bevor du in die Details gehst. "
"Wenn du Ergebnisse hast, schreibe sie SOFORT mit write_file — beschreibe nicht, "
"was du schreiben wirst, schreibe es."
)
if args.mode == "builtin":
system_prompt += (
"\nZusaetzlich steht spawn_subagent zur Verfuegung: Starte einen "
"Subagenten mit eigenem Kontext fuer isolierte Teilaufgaben (z.B. "
"Analyse eines einzelnen Moduls). Der Subagent kann nur lesen, nicht "
"schreiben. Nutze Subagenten, um die Breite der Analyse zu erhoehen: "
"delegiere Modul-Analysen an Subagenten, waehrend du die "
"Gesamtstruktur und die Ergebnisdateien verwaltest."
)
elif args.mode == "custom":
# Custom agents laden
if args.agents:
custom_prompts = load_custom_agents(args.agents)
SUBAGENT_SYSTEM_PROMPTS.clear()
SUBAGENT_SYSTEM_PROMPTS.update(custom_prompts)
agent_list = ", ".join(SUBAGENT_SYSTEM_PROMPTS.keys())
sys.stderr.write(f"[glm-kimi-adapter] Custom agents geladen: {agent_list}\n")
system_prompt += (
f"\nDu orchestrierst spezialisierte Subagenten. Verfuegbare Agenten-Typen: "
f"{agent_list}. Nutze spawn_subagent mit dem passenden subagent_type, "
f"um Teilaufgaben zu delegieren. Vorgehen: "
f"1. Starte 'modulinventar' fuer das vollstaendige Inventar. "
f"2. Starte 'faktenermittler' fuer Modulausschnitte, die du analysieren willst. "
f"3. Schreibe die Anforderungen selbst mit write_file (die Autoren-Agenten "
f"sind nur fuer Vorbereitung da, nicht fuer das Schreiben der Ergebnisdateien). "
f"4. Fuehre den Konsistenzcheck selbst durch. "
f"Der Subagent kann nur lesen, nicht schreiben."
)
else:
sys.stderr.write("[glm-kimi-adapter] WARNUNG: Modus 'custom' ohne --agents, falle auf 'builtin' zurueck\n")
args.mode = "builtin"
system_prompt += (
"\nZusaetzlich steht spawn_subagent zur Verfuegung."
)
system_prompt += (
"\nDie Codebasis wird ausschliesslich GELESEN. Schreibe Ergebnisdateien mit "
"write_file ins Ausgabeverzeichnis. Sprache: Deutsch fuer Anforderungen."
)
@@ -582,13 +924,14 @@ def main():
sys.stderr.write(f"[glm-kimi-adapter] Provider: {provider['name']}\n")
sys.stderr.write(f"[glm-kimi-adapter] Modell: {args.model}\n")
sys.stderr.write(f"[glm-kimi-adapter] Effort: {args.effort}\n")
sys.stderr.write(f"[glm-kimi-adapter] Mode: {args.mode}\n")
try:
result = run_agent_loop(
provider=provider, model=args.model, system_prompt=system_prompt,
user_prompt=user_prompt, api_key=api_key, effort=args.effort,
root=args.root, output_dir=str(output_dir), max_turns=args.max_turns,
temperature=args.temperature, timeout=args.timeout,
temperature=args.temperature, timeout=args.timeout, mode=args.mode,
)
except Exception as e:
tb = traceback.format_exc()
@@ -602,7 +945,10 @@ def main():
"reasoning_tokens": 0},
"modelUsage": {}, "tool_calls": [], "tool_call_count": 0,
"tool_call_types": {}, "written_files": [], "result": "",
"errors": [str(e)], "adapter": "python-glm-kimi", "adapter_version": "1.0.0",
"errors": [str(e)], "adapter": "python-glm-kimi", "adapter_version": "1.1.0",
"mode": args.mode,
"subagent_stats": {"spawned": 0, "completed": 0, "failed": 0, "by_type": {}},
"subagent_details": [],
}
end_iso = datetime.now(timezone.utc).isoformat()
@@ -616,6 +962,8 @@ def main():
sys.stderr.write(f"[glm-kimi-adapter] Turns: {result['num_turns']}\n")
sys.stderr.write(f"[glm-kimi-adapter] Tokens gesamt: {result['usage']['total_tokens']:,}\n")
sys.stderr.write(f"[glm-kimi-adapter] Tool-Calls: {result['tool_call_count']}\n")
sa = result.get("subagent_stats", {})
sys.stderr.write(f"[glm-kimi-adapter] Subagenten: {sa.get('spawned',0)} (completed: {sa.get('completed',0)}, failed: {sa.get('failed',0)})\n")
sys.stderr.write(f"[glm-kimi-adapter] Ergebnisdateien: {len(result['written_files'])}\n")
sys.stderr.write(f"[glm-kimi-adapter] RawResult: {raw_result_path}\n")