Prompt History und Verbrauchsauswertung (#660), englische Hauptsprache (#661)

Prompt history: every run records its transcript (task, each model step with
tokens and cost, tool calls and results, engine follow-ups) - bounded in size,
scrubbed of recognisable secrets, system prompts stored once per text.
Retention in days per instance (default 14) and per agent (0 = never, -1 =
forever); an hourly janitor deletes what is past it, "never" also purges.

Usage analysis over run receipts: filter by period, agent, model, event, status,
cost and free text; sort by every column; totals over the whole filter; hourly
or daily series grouped by agent, model or event; markers for budget denials and
model changes; run detail with receipt, transcript, tool calls and approvals.

WebUI: Analysis > Token usage > Runs / Timeline, run detail, Prompt History with
retention settings. New code, comments and texts in English; texts come from
language packs (i18n/en.json, de.json) via modules/i18n.js. CLAUDE.md records
English as the main language.

Version 0.1.12.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-19 15:33:09 +02:00
co-authored by Claude Opus 5
parent 2f0361278b
commit 5adf4c1ca6
40 changed files with 3521 additions and 10 deletions
+24
View File
@@ -98,6 +98,12 @@ public sealed class AppHost : IAsyncDisposable
public TaskboardService? Board { get; private init; }
public SqliteUsageRepository? Usage { get; private init; }
/// <summary>#660: Prompt history - prompt and response of every run.</summary>
public Core.Transcripts.IRunTranscriptStore? Transcripts { get; private init; }
/// <summary>#660: Usage analysis, run details and prompt history for the user interfaces.</summary>
public RunInsights? Insights { get; private init; }
/// <summary>Modellpreise von OpenRouter - fuer Budget und Kostenprognose (#595). Laedt im Hintergrund.</summary>
public ModelPricingCatalog? Pricing { get; private init; }
@@ -525,6 +531,7 @@ public sealed class AppHost : IAsyncDisposable
var dauerfreigaben = new SqliteDauerfreigabeRepository(storage);
var stagingGate = new StagingGate(new StagingPolicy(), stagingRepository, dauerfreigaben: dauerfreigaben);
var usage = new SqliteUsageRepository(storage);
var transcripts = new Core.Transcripts.SqliteRunTranscriptStore(storage);
// Preise fürs Budget: Ohne sie greift nur die Token-Grenze.
var pricing = new ModelPricingCatalog();
@@ -539,6 +546,8 @@ public sealed class AppHost : IAsyncDisposable
memory, usage, pricing, taskRepository, audit, stagingGate)
{
InstanceBudget = instance.Budget,
TranscriptStore = transcripts,
DefaultTranscriptRetention = () => instance.PromptHistory.RetentionDays,
MaxDelegationDepth = instance.MaxDelegationDepth,
DelegationGateTimeout = TimeSpan.FromSeconds(instance.DelegationGateTimeoutSeconds)
};
@@ -574,6 +583,11 @@ public sealed class AppHost : IAsyncDisposable
Staging = staging,
Board = board,
Usage = usage,
Transcripts = transcripts,
Insights = new RunInsights(audit, transcripts, board, staging,
agentId => instance.Agents.FirstOrDefault(a => a.AgentId == agentId) is { } agent
? engine.TranscriptRetentionFor(agent)
: Core.Transcripts.PromptHistorySettings.Normalize(instance.PromptHistory.RetentionDays)),
Pricing = pricing,
TelegramClient = telegram,
Status = new OpenRouterStatusService(instance.OpenRouterApiKey),
@@ -591,6 +605,16 @@ public sealed class AppHost : IAsyncDisposable
host._shutdown.Add(() => { openRouter.Dispose(); return ValueTask.CompletedTask; });
// #660: Retention of the prompt history - per agent, falling back to the instance.
var janitor = new Core.Transcripts.TranscriptJanitor(
transcripts,
agentId => instance.Agents.FirstOrDefault(a => a.AgentId == agentId) is { } agent
? engine.TranscriptRetentionFor(agent)
: Core.Transcripts.PromptHistorySettings.Normalize(instance.PromptHistory.RetentionDays),
loggerFactory.CreateLogger("ClawdDotNet.Core.Transcripts"));
janitor.Start();
host._shutdown.Add(janitor.DisposeAsync);
// #593: Angelegt wurde der Dienst schon immer, gestartet nie — weder kopflos noch
// mit Fenster. Die Kopfzeile der Weboberflaeche stand deshalb dauerhaft auf
// "OpenRouter: —" und /api/status auf "Prüfe…".