using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Gott.Services;
///
/// Sammelt Live-Telemetrie laufender dynamischer Workflows ("ultracode"): die Aktivität der
/// Subagenten, die die Claude CLI über das Workflow-/Task-Tool spawnt. Diese Subagent-Events
/// werden im Haupt-Stream bewusst unterdrückt (parent_tool_use_id-Guard in ClaudeCliService) –
/// hier werden sie abgegriffen, damit der Workflow-Fortschritt LIVE (während des Laufs) und
/// danach (persistiert in chains.workflow_info) abrufbar ist. In-Memory pro Session, analog
/// StreamingTracker. Telemetrie darf den Stream nie blockieren → alles defensiv + lock-leicht.
///
public class WorkflowProgressTracker
{
private readonly ConcurrentDictionary _progress
= new ConcurrentDictionary();
///
/// Verbucht ein Subagent-Event (Tool-Aufrufe + Token-Nutzung eines Subagenten).
/// Lazy: legt den Eintrag bei der ERSTEN Subagent-Aktivität einer Session an, d.h. nur
/// echte Workflow-Läufe (mit gespawnten Subagenten) erzeugen überhaupt einen Eintrag.
///
public void RecordSubagent(string sessionId, string chainId, string parentToolUseId,
IReadOnlyList toolNames, long inputTokens, long outputTokens)
{
if (string.IsNullOrEmpty(sessionId)) return;
var wp = _progress.GetOrAdd(sessionId, _ => new WorkflowProgress
{
ChainId = chainId,
StartedAt = DateTime.UtcNow
});
lock (wp.Sync)
{
// Stale-Eintrag eines früheren, nicht sauber finalisierten Laufs (gleiche Session,
// andere Chain) → atomar für die neue Chain zurücksetzen, statt fälschlich zu mischen.
if (wp.ChainId != chainId)
{
wp.ChainId = chainId;
wp.StartedAt = DateTime.UtcNow;
wp.SubagentIds.Clear();
wp.SubagentCount = 0;
wp.SubagentToolCalls = 0;
wp.SubagentInputTokens = 0;
wp.SubagentOutputTokens = 0;
wp.ToolBreakdown.Clear();
wp.RecentTools.Clear();
}
if (!string.IsNullOrEmpty(parentToolUseId) && wp.SubagentIds.Add(parentToolUseId))
wp.SubagentCount = wp.SubagentIds.Count;
if (toolNames != null)
{
foreach (var tn in toolNames)
{
if (string.IsNullOrEmpty(tn)) continue;
wp.SubagentToolCalls++;
wp.ToolBreakdown[tn] = wp.ToolBreakdown.TryGetValue(tn, out var c) ? c + 1 : 1;
wp.RecentTools.Add(tn);
if (wp.RecentTools.Count > 12) wp.RecentTools.RemoveAt(0);
}
}
wp.SubagentInputTokens += inputTokens;
wp.SubagentOutputTokens += outputTokens;
wp.LastActivityAt = DateTime.UtcNow;
}
}
/// Threadsicherer Live-Snapshot – null, wenn für die Session kein Workflow läuft.
public WorkflowProgress GetSnapshot(string sessionId)
=> _progress.TryGetValue(sessionId, out var wp) ? wp.Snapshot() : null;
/// Aktuellen Stand als JSON serialisieren (threadsicher) – null wenn kein Eintrag.
public string GetJson(string sessionId)
=> _progress.TryGetValue(sessionId, out var wp) ? wp.ToJson() : null;
/// ChainId des laufenden Workflow-Eintrags (für die Zuordnung beim Finalize).
public string ChainIdFor(string sessionId)
=> _progress.TryGetValue(sessionId, out var wp) ? wp.ChainId : null;
/// Entfernt den Eintrag (beim Chain-Finalize aufzurufen).
public void End(string sessionId) => _progress.TryRemove(sessionId, out _);
}
///
/// Telemetrie eines dynamischen Workflows: Subagent-Anzahl, Tool-Aufrufe (+ Aufschlüsselung)
/// und aggregierte Subagent-Tokens. Wird live geführt und beim Chain-Finalize als JSON in
/// chains.workflow_info persistiert.
///
public class WorkflowProgress
{
[JsonIgnore] public object Sync { get; } = new object();
// Dedup-Hilfsstruktur (nicht persistiert; SubagentCount trägt den Wert).
[JsonIgnore] public HashSet SubagentIds { get; set; } = new HashSet();
public string ChainId { get; set; }
public DateTime StartedAt { get; set; }
public DateTime LastActivityAt { get; set; }
public int SubagentCount { get; set; }
public int SubagentToolCalls { get; set; }
public long SubagentInputTokens { get; set; }
public long SubagentOutputTokens { get; set; }
public Dictionary ToolBreakdown { get; set; } = new Dictionary();
public List RecentTools { get; set; } = new List();
public WorkflowProgress Snapshot()
{
lock (Sync)
{
return new WorkflowProgress
{
ChainId = ChainId,
StartedAt = StartedAt,
LastActivityAt = LastActivityAt,
SubagentCount = SubagentCount,
SubagentToolCalls = SubagentToolCalls,
SubagentInputTokens = SubagentInputTokens,
SubagentOutputTokens = SubagentOutputTokens,
ToolBreakdown = new Dictionary(ToolBreakdown),
RecentTools = new List(RecentTools)
};
}
}
// Serialisiert einen entkoppelten Snapshot (Snapshot() kopiert unter Lock) – so läuft die
// Serialisierung außerhalb jeder kritischen Sektion und kann nie auf eine live mutierte
// Collection treffen.
public string ToJson() => JsonSerializer.Serialize(Snapshot());
}