← All source files

V9 / fa079a940f8f

SubagentRegistry.cs

Lilith/Core/Scenarios/SubagentRegistry.cs

Unchanged from the preceding version
CS
namespace Lilith.Core.Scenarios;

public sealed record SubagentRun(long AgentId, string RunId, string ScenarioId, string Task, string Status,
    DateTimeOffset StartedAt, DateTimeOffset? FinishedAt = null);
public sealed record SubagentSnapshot(int RunningCount, long TotalCreated, long CompletedCount, long FailedCount,
    long CanceledCount, long WaitingApprovalCount, SubagentRun[] Running, SubagentRun[] Recent);
public sealed record SubagentOutput(long AgentId, string RunId, string Kind, string Text, bool IsSummary);

/// <summary>Session-level execution registry. IDs and conversations are never reused.</summary>
public sealed class SubagentRegistry
{
    private readonly object _sync = new();
    private readonly Dictionary<string, SubagentRun> _active = new(StringComparer.Ordinal);
    private readonly Queue<SubagentRun> _recent = new();
    private long _created, _completed, _failed, _canceled, _waiting;

    public SubagentSnapshot Snapshot()
    {
        lock (_sync) return new(_active.Count, _created, _completed, _failed, _canceled, _waiting,
            _active.Values.OrderBy(r => r.AgentId).ToArray(), _recent.Reverse().ToArray());
    }

    internal SubagentRun? Observe(ScenarioProgress progress)
    {
        lock (_sync)
        {
            if (progress.Kind == "Started")
            {
                if (_active.ContainsKey(progress.RunId)) throw new InvalidOperationException("Subagent run already started.");
                var started = new SubagentRun(++_created, progress.RunId, progress.ScenarioId, progress.Text, "running", DateTimeOffset.UtcNow);
                _active.Add(progress.RunId, started);
                return started;
            }
            if (!_active.TryGetValue(progress.RunId, out var run)) return null;
            string? status = progress.Kind switch { "Completed" => "completed", "Failed" => "failed", "Canceled" => "canceled", "NeedsApproval" => "needs_approval", _ => null };
            if (status is null) return run;
            _active.Remove(progress.RunId);
            var finished = run with { Status = status, FinishedAt = DateTimeOffset.UtcNow };
            switch (status)
            {
                case "completed": _completed++; break;
                case "failed": _failed++; break;
                case "canceled": _canceled++; break;
                case "needs_approval": _waiting++; break;
            }
            _recent.Enqueue(finished);
            if (_recent.Count > 128) _recent.Dequeue();
            return finished;
        }
    }

    internal static SubagentOutput? Present(ScenarioProgress progress, SubagentRun? run, bool details)
    {
        if (run is null) return null;
        string? outcome = progress.Kind switch { "Completed" => "completed", "Failed" => "failed", "Canceled" => "canceled", "NeedsApproval" => "is waiting for approval for", _ => null };
        if (outcome is not null) return new(run.AgentId, run.RunId, progress.Kind,
            $"Subagent {run.AgentId} {outcome} {run.Task} ({run.ScenarioId}).", true);
        return details ? new(run.AgentId, run.RunId, progress.Kind, progress.Text, false) : null;
    }
}