using ClawdDotNet.Core.Analytics; using ClawdDotNet.Core.Audit; using ClawdDotNet.Core.Staging; using ClawdDotNet.Core.Tasks; using ClawdDotNet.Core.Transcripts; namespace ClawdDotNet.App.Services; /// Everything known about a single run (#660). public sealed record RunDetail( string RunId, RunReceipt? Receipt, string? TaskTitle, IReadOnlyList ToolCalls, IReadOnlyList Approvals, RunTranscript? Transcript, string? SystemPrompt, string TranscriptState, // #697: The runs this one delegated to — the way down to the originals. IReadOnlyList? CalledRuns = null); /// /// Read side of the usage analysis and the prompt history (#660) for both user interfaces. /// Owns no data: receipts and audit come from the audit repository, transcripts from the /// transcript store, task titles from the board. /// public sealed class RunInsights( IAuditRepository audit, IRunTranscriptStore? transcripts, TaskboardService? board, StagingService? staging, Func retentionFor) { public UsageAnalytics Analytics { get; } = new(audit, board is null ? null : async (id, ct) => (await board.GetAsync(id, ct))?.Title); public IRunTranscriptStore? Transcripts => transcripts; public async Task GetRunAsync(string runId, CancellationToken ct) { var receipt = await audit.GetReceiptForRunAsync(runId, ct); var calls = await audit.ListForRunAsync(runId, ct); var transcript = transcripts is null ? null : await transcripts.GetAsync(runId, ct); if (receipt is null && calls.Count == 0 && transcript is null) return null; var agentId = receipt?.AgentId ?? transcript?.AgentId ?? calls.FirstOrDefault()?.AgentId ?? ""; var taskId = receipt?.TaskId ?? transcript?.TaskId; return new RunDetail( runId, receipt, taskId is not null && board is not null ? (await board.GetAsync(taskId, ct))?.Title : null, // Only the completed calls; the "started" markers (#554) would double every line. calls.Where(c => c.Status != AuditStatus.Started).ToList(), staging is null ? [] : await staging.ListForRunAsync(runId, ct), transcript, transcript?.SystemPromptHash is { } hash && transcripts is not null ? await transcripts.GetSystemPromptAsync(hash, ct) : null, TranscriptState(transcript, agentId), await audit.ListReceiptsCalledFromAsync(runId, ct)); } /// Why there is or is not a transcript - the UI says it instead of showing an empty box. private string TranscriptState(RunTranscript? transcript, string agentId) { if (transcript is not null) return "recorded"; if (transcripts is null) return "unavailable"; return retentionFor(agentId) == PromptHistorySettings.Never ? "disabled" : "missing"; } }