Befunde aus dem Testbetrieb auf der Linux-VM behoben (#572)

- #578: Abgebrochene Laeufe (Schrittgrenze, Zeitueberschreitung, Abbruch,
  Ausnahme) tragen ihre bezahlten Token - vorher 0 in RunUsage, fuer das
  Budget unsichtbar.
- #580: WebFetch prueft die aufgeloeste Zieladresse beim Verbinden; ein
  erlaubter Name, der auf 127.0.0.1 aufloest, wird gesperrt.
- #575: Dateien entstehen unter Unix mit 0600 (AtomicFile, Protokolle,
  FileRW-Anhaengen, state.db), Protokollverzeichnisse mit 0700.
- #576: SIGTERM/SIGINT beenden auch mit Fenster geordnet.
- #593: OpenRouter-Statusdienst wird gestartet.
- #589: Auftragsnachricht nennt aktuelle Zeit, Zeitzone und Termin.
- #581: Unvollstaendiges Argument-JSON erreicht das Werkzeug nicht mehr;
  das Modell bekommt eine verstaendliche Rueckmeldung statt Parsertext.
- #591: Weboberflaeche liest die Felder, die API und Ereignisstrom liefern
  (Freigaben, Protokoll, Taskboard mit Termin, Werkzeug-Einstellungen,
  Tokennutzung); Ereignisstrom in camelCase.
- #592: Chat zeigt Nachrichten nicht mehr doppelt.
- #536 (teilweise): A2A-Antworten tragen Absender und Empfaenger, Parser
  wirft nicht mehr; Datums- und Volltextfilter in Agenten-Chats.
- deploy/publish.py: Packager-Pfad net10.0, Schalter --agent-file.
- Version 0.1.4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-14 15:40:56 +02:00
co-authored by Claude Opus 5
parent e2567a466f
commit 6581fd0187
29 changed files with 1128 additions and 190 deletions
@@ -1,6 +1,10 @@
// ClawdDotNet — Token-Verbrauch & Analyse
//
// Diagramme (CSS/DOM-Balken ohne externe Bibliothek), Statistiken und Läufetabelle.
//
// Feldnamen wie UsageDto (#591): costUsd, costIsKnown, occurredAt, status. Eine
// Gesamtsumme liefert die API nicht — sie ist Prompt + Completion. Vorher las die Ansicht
// totalTokens, cost und timestamp und zeigte überall 0 bzw. "—".
import { api } from "../modules/api.js";
import {
@@ -26,6 +30,10 @@ export async function rendereVerbrauch() {
await ladeVerbrauch();
}
function gesamtTokens(lauf) {
return (lauf.promptTokens || 0) + (lauf.completionTokens || 0);
}
async function ladeVerbrauch() {
const container = document.getElementById("verbrauch-inhalt-bereich");
if (!container) return;
@@ -55,32 +63,45 @@ async function ladeVerbrauch() {
let summePrompt = 0;
let summeCompletion = 0;
let summeKosten = 0;
let unbekannteKosten = 0;
const nachAgent = new Map();
const nachModell = new Map();
for (const l of laeufe) {
summeTokens += l.totalTokens || 0;
const tokens = gesamtTokens(l);
summeTokens += tokens;
summePrompt += l.promptTokens || 0;
summeCompletion += l.completionTokens || 0;
summeKosten += Number(l.cost || 0);
summeKosten += Number(l.costUsd || 0);
if (!l.costIsKnown) unbekannteKosten++;
const agentKey = l.agentId || "Unbekannt";
nachAgent.set(agentKey, (nachAgent.get(agentKey) || 0) + (l.totalTokens || 0));
nachAgent.set(agentKey, (nachAgent.get(agentKey) || 0) + tokens);
const modellKey = l.model || "Unbekannt";
nachModell.set(modellKey, (nachModell.get(modellKey) || 0) + (l.totalTokens || 0));
nachModell.set(modellKey, (nachModell.get(modellKey) || 0) + tokens);
}
// 1. Kacheln (Statistiken)
const kachelGitter = erstelleElement("div", { klasse: "kachel-gitter" });
kachelGitter.append(
erstelleStatKachel("Gesamtkosten (100 Läufe)", formatiereKosten(summeKosten)),
erstelleStatKachel(`Gesamtkosten (${laeufe.length} Läufe)`, formatiereKosten(summeKosten)),
erstelleStatKachel("Tokens gesamt", formatiereTokens(summeTokens)),
erstelleStatKachel("Eingabe-Tokens (Prompt)", formatiereTokens(summePrompt)),
erstelleStatKachel("Ausgabe-Tokens (Completion)", formatiereTokens(summeCompletion))
);
const hinweise = [];
if (unbekannteKosten > 0) {
hinweise.push(
erstelleElement("p", {
klasse: "gedaempft text-klein",
text: `${unbekannteKosten} Lauf/Läufe ohne bekannten Preis — deren Kosten sind in der Summe nicht enthalten.`
})
);
}
// 2. Balkendiagramme (rein CSS/DOM)
const diagrammGitter = erstelleElement("div", { klasse: "diagramm-gitter" });
@@ -99,6 +120,7 @@ async function ladeVerbrauch() {
erstelleElement("th", { text: "Zeitpunkt" }),
erstelleElement("th", { text: "Agent" }),
erstelleElement("th", { text: "Modell" }),
erstelleElement("th", { text: "Status" }),
erstelleElement("th", { text: "Prompt", stil: "text-align: right;" }),
erstelleElement("th", { text: "Completion", stil: "text-align: right;" }),
erstelleElement("th", { text: "Gesamt", stil: "text-align: right;" }),
@@ -109,15 +131,20 @@ async function ladeVerbrauch() {
const tbody = erstelleElement("tbody");
for (const lauf of laeufe) {
const kosten = lauf.costIsKnown
? formatiereKosten(lauf.costUsd)
: `${formatiereKosten(lauf.costUsd)} (Preis unbekannt)`;
const tr = erstelleElement("tr");
tr.append(
erstelleElement("td", { text: formatiereDatumZeit(lauf.timestamp) }),
erstelleElement("td", { text: formatiereDatumZeit(lauf.occurredAt) }),
erstelleElement("td", {}, erstelleElement("strong", { text: lauf.agentId })),
erstelleElement("td", { klasse: "gedaempft text-klein", text: lauf.model }),
erstelleElement("td", { klasse: "text-klein", text: lauf.status || "—" }),
erstelleElement("td", { stil: "text-align: right;", text: formatiereTokens(lauf.promptTokens) }),
erstelleElement("td", { stil: "text-align: right;", text: formatiereTokens(lauf.completionTokens) }),
erstelleElement("td", { stil: "text-align: right;", text: formatiereTokens(lauf.totalTokens) }),
erstelleElement("td", { stil: "text-align: right;", text: formatiereKosten(lauf.cost) })
erstelleElement("td", { stil: "text-align: right;", text: formatiereTokens(gesamtTokens(lauf)) }),
erstelleElement("td", { stil: "text-align: right;", text: kosten })
);
tbody.append(tr);
}
@@ -125,7 +152,7 @@ async function ladeVerbrauch() {
tabelle.append(thead, tbody);
tabelleKarte.append(tabelle);
container.append(kachelGitter, diagrammGitter, tabelleKarte);
container.append(kachelGitter, ...hinweise, diagrammGitter, tabelleKarte);
} catch (err) {
container.replaceChildren(
erstelleKarte("Verbrauchsdaten nicht verfügbar", [