Workflow-Totalverlust beheben: inkrementelle Persistenz + Token-Fix + Resume
Bisher wurden Workflow-Subagent-Outputs nur beim sauberen FinalizeChain in die DB gerettet - bei Abbruch (529/Timeout/Stop/Hard-Kill) war alles weg, Tokens standen auf 0. - A) Inkrementelle Persistenz: MaybeFlushWorkflowProgress() (throttled 6s) schreibt workflow_info + Token + runId waehrend des Laufs -> ueberlebt Hard-Kill (OOM/Redeploy), wo kein catch/finally laeuft. - B) Output-Rettung: ReadLive liest jetzt vollstaendige Subagent-Outputs aus journal.jsonl (nicht nur Counts), inkl. State. - C) Token-Fix: neuer assistant-Branch -> CliEventType.Usage; EffectiveUsage() rettet Orchestrator-Token bei Abbruch statt 0. - D) Resume: Feld/Migration workflow_run_id; FinalizeChain persistiert die runId; naechster Chat injiziert einen resumeFromRunId-Hinweis. - UI: Workflow-Status-Modal zeigt geretteten Output + runId/"fortsetzbar"-Banner. Build 0/0, ReadLive gegen echtes journal.jsonl verifiziert, adversarial 2x geprueft. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
5f7dec6448
commit
3ae94fb697
9 files changed
+217
-19
No files matched your search
@@ -90,9 +90,9 @@ public static class WorkflowTelemetryReader
|
||||
{
|
||||
if (run == null) return null;
|
||||
var done = ReadCompleted(run.RichJsonPath);
|
||||
if (done != null) { done.ChainId = run.ChainId; done.StartedAt = run.StartedAt; return done; }
|
||||
if (done != null) { done.ChainId = run.ChainId; done.RunId = run.RunId; done.StartedAt = run.StartedAt; return done; }
|
||||
var live = ReadLive(run.JournalPath, run.StartedAt);
|
||||
if (live != null) { live.ChainId = run.ChainId; return live; }
|
||||
if (live != null) { live.ChainId = run.ChainId; live.RunId = run.RunId; return live; }
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -158,14 +158,23 @@ public static class WorkflowTelemetryReader
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
/// <summary>Live-Fortschritt aus dem append-only journal.jsonl (started/result pro Subagent).</summary>
|
||||
/// <summary>
|
||||
/// Live-Fortschritt aus dem append-only journal.jsonl (started/result pro Subagent).
|
||||
/// WICHTIG: Die <c>result</c>-Events enthalten den VOLLSTÄNDIGEN Subagent-Output (Feld
|
||||
/// <c>result</c>, Objekt oder String) – die einzige Quelle, die schon WÄHREND des Laufs
|
||||
/// existiert. Wir bauen daraus die Per-Agent-Liste inkl. Output-Preview, damit bei einem
|
||||
/// Abbruch die bereits geleistete Arbeit fertiger Subagenten NICHT verloren geht (die reiche
|
||||
/// <runId>.json mit Tokens/Labels wird erst bei Completion geschrieben). Tokens/Labels
|
||||
/// sind hier daher noch 0/agentId – die kennt nur die abgeschlossene <runId>.json.
|
||||
/// </summary>
|
||||
public static WorkflowProgress ReadLive(string journalPath, DateTime startedAt)
|
||||
{
|
||||
if (string.IsNullOrEmpty(journalPath) || !File.Exists(journalPath)) return null;
|
||||
try
|
||||
{
|
||||
var started = new HashSet<string>();
|
||||
var done = new HashSet<string>();
|
||||
var order = new List<string>(); // Reihenfolge der agentIds (started/result)
|
||||
var seen = new HashSet<string>();
|
||||
var outputs = new Dictionary<string, string>(); // agentId -> voller Output (nur fertige)
|
||||
// ReadAllText statt ReadAllLines: robust gegen halb geschriebene letzte Zeile.
|
||||
foreach (var raw in File.ReadAllText(journalPath).Split('\n'))
|
||||
{
|
||||
@@ -177,18 +186,39 @@ public static class WorkflowTelemetryReader
|
||||
var t = GetStr(ev, "type");
|
||||
var aid = GetStr(ev, "agentId");
|
||||
if (string.IsNullOrEmpty(aid)) continue;
|
||||
if (t == "started") started.Add(aid);
|
||||
else if (t == "result") done.Add(aid);
|
||||
if (seen.Add(aid)) order.Add(aid);
|
||||
if (t == "result")
|
||||
{
|
||||
string output = null;
|
||||
if (ev.TryGetProperty("result", out var rv))
|
||||
output = rv.ValueKind == JsonValueKind.String ? rv.GetString() : rv.GetRawText();
|
||||
outputs[aid] = output ?? "";
|
||||
}
|
||||
}
|
||||
if (started.Count == 0 && done.Count == 0) return null;
|
||||
return new WorkflowProgress
|
||||
if (order.Count == 0) return null;
|
||||
var wp = new WorkflowProgress
|
||||
{
|
||||
Status = "running",
|
||||
StartedAt = startedAt,
|
||||
SubagentCount = started.Count,
|
||||
CompletedCount = done.Count,
|
||||
SubagentCount = order.Count,
|
||||
CompletedCount = outputs.Count,
|
||||
DurationMs = (long)Math.Max(0, (DateTime.UtcNow - startedAt).TotalMilliseconds)
|
||||
};
|
||||
int idx = 0;
|
||||
foreach (var aid in order)
|
||||
{
|
||||
var isDone = outputs.TryGetValue(aid, out var outp);
|
||||
wp.Agents.Add(new WorkflowAgentInfo
|
||||
{
|
||||
Index = ++idx,
|
||||
Label = aid,
|
||||
State = isDone ? "done" : "running",
|
||||
// Voller Output fertiger Subagenten (großzügig gekappt), damit die Arbeit
|
||||
// bei Abbruch rekonstruierbar bleibt statt verloren zu gehen.
|
||||
ResultPreview = isDone ? Trim(outp, 1500) : null
|
||||
});
|
||||
}
|
||||
return wp;
|
||||
}
|
||||
catch { return null; }
|
||||
}
|
||||
@@ -215,6 +245,7 @@ public class WorkflowProgress
|
||||
{
|
||||
public string Status { get; set; } // "running" | "completed"
|
||||
public string ChainId { get; set; }
|
||||
public string RunId { get; set; } // wf_… – für Resume nach Abbruch
|
||||
public string WorkflowName { get; set; }
|
||||
public string Summary { get; set; }
|
||||
public DateTime StartedAt { get; set; }
|
||||
|
||||
Reference in new issue
Block a user