- #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>
430 lines
13 KiB
JavaScript
430 lines
13 KiB
JavaScript
// ClawdDotNet — Agenten-Verwaltung
|
|
//
|
|
// Übersicht, Anlegen, Löschen von Agenten, Werkzeug-Schalter und maskierte Einstellungen.
|
|
|
|
import { api } from "../modules/api.js";
|
|
import { abonnieren } from "../modules/events.js";
|
|
import {
|
|
erstelleElement,
|
|
erstelleIcon,
|
|
PFADE,
|
|
erstellePlakette,
|
|
erstelleKarte,
|
|
erstelleModal,
|
|
zeigeMeldung
|
|
} from "../modules/dom.js";
|
|
|
|
let gewaehlterAgentId = null;
|
|
let agenten = [];
|
|
let entladeEreignisse = [];
|
|
|
|
export async function rendereAgenten(parameter = {}) {
|
|
const container = document.getElementById("ansicht-inhalt");
|
|
if (!container) return;
|
|
container.replaceChildren();
|
|
|
|
entladeEreignisse.forEach((c) => c());
|
|
entladeEreignisse = [];
|
|
|
|
const layout = erstelleElement("div", { klasse: "agenten-ansicht" });
|
|
|
|
const kopfZeile = erstelleElement("div", { klasse: "ansicht-kopf" });
|
|
kopfZeile.append(
|
|
erstelleElement("h1", { text: "Agenten" }),
|
|
erstelleElement(
|
|
"button",
|
|
{
|
|
klasse: "schaltflaeche primaer",
|
|
onclick: oeffneNeuerAgentDialog
|
|
},
|
|
erstelleIcon(PFADE.plus, 16),
|
|
erstelleElement("span", { text: " Neuer Agent" })
|
|
)
|
|
);
|
|
|
|
const raster = erstelleElement("div", { klasse: "agenten-raster" });
|
|
const listeContainer = erstelleElement("div", { klasse: "agenten-liste-bereich" });
|
|
const detailContainer = erstelleElement("div", { klasse: "agenten-detail-bereich" });
|
|
|
|
raster.append(listeContainer, detailContainer);
|
|
layout.append(kopfZeile, raster);
|
|
container.append(layout);
|
|
|
|
await ladeUndRendereListe(parameter.agentId);
|
|
|
|
entladeEreignisse.push(
|
|
abonnieren("instance", (daten) => {
|
|
if (daten.kind === "Agents") {
|
|
ladeUndRendereListe(gewaehlterAgentId);
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
async function ladeUndRendereListe(zielId = null) {
|
|
const listeContainer = document.querySelector(".agenten-liste-bereich");
|
|
if (!listeContainer) return;
|
|
|
|
listeContainer.replaceChildren(
|
|
erstelleElement("div", { klasse: "hinweis-zentriert", text: "Lade Agenten…" })
|
|
);
|
|
|
|
try {
|
|
agenten = await api("/agents");
|
|
listeContainer.replaceChildren();
|
|
|
|
if (!agenten || agenten.length === 0) {
|
|
const leerKasten = erstelleElement("div", { klasse: "hinweis-zentriert" });
|
|
leerKasten.append(
|
|
erstelleElement("p", { klasse: "gedaempft", text: "In dieser Instanz sind noch keine Agenten eingerichtet." }),
|
|
erstelleElement("button", {
|
|
klasse: "schaltflaeche primaer klein",
|
|
onclick: oeffneNeuerAgentDialog,
|
|
style: "margin-top: 10px;"
|
|
}, erstelleIcon(PFADE.plus, 14), erstelleElement("span", { text: " Ersten Agenten anlegen" }))
|
|
);
|
|
listeContainer.append(leerKasten);
|
|
rendereDetails(null);
|
|
return;
|
|
}
|
|
|
|
const auswahlId = zielId && agenten.some((a) => a.agentId === zielId)
|
|
? zielId
|
|
: agenten[0].agentId;
|
|
|
|
for (const a of agenten) {
|
|
const eintrag = erstelleElement("div", {
|
|
klasse: `agenten-karte ${a.agentId === auswahlId ? "aktiv" : ""}`,
|
|
onclick: () => {
|
|
document.querySelectorAll(".agenten-karte").forEach((k) => k.classList.remove("aktiv"));
|
|
eintrag.classList.add("aktiv");
|
|
rendereDetails(a.agentId);
|
|
}
|
|
});
|
|
|
|
const top = erstelleElement("div", { klasse: "agent-karte-top" });
|
|
const name = erstelleElement("strong", { text: a.displayName });
|
|
const status = a.isRunning
|
|
? erstellePlakette("Beschäftigt", "warnung")
|
|
: erstellePlakette("Bereit", "neutral");
|
|
top.append(name, status);
|
|
|
|
const meta = erstelleElement("div", { klasse: "agent-karte-meta" });
|
|
meta.append(
|
|
erstelleElement("span", { klasse: "gedaempft", text: `ID: ${a.agentId}` }),
|
|
erstelleElement("span", { klasse: "gedaempft", text: ` · Modell: ${a.model}` })
|
|
);
|
|
|
|
const werkzeugeZaehler = a.tools ? a.tools.filter((t) => t.enabled).length : 0;
|
|
const werkzeugeText = erstelleElement("div", {
|
|
klasse: "agent-karte-werkzeuge gedaempft",
|
|
text: `${werkzeugeZaehler} Werkzeuge aktiv`
|
|
});
|
|
|
|
eintrag.append(top, meta, werkzeugeText);
|
|
listeContainer.append(eintrag);
|
|
}
|
|
|
|
rendereDetails(auswahlId);
|
|
} catch (err) {
|
|
const text = err.instanzUnerreichbar
|
|
? "Die ausgewählte Instanz läuft derzeit nicht. Bitte starte sie zuerst im Kopfbereich oder unter 'Instanzen'."
|
|
: (err.instanzKonflikt
|
|
? "Keine aktive Instanz gewählt. Bitte wähle oben eine Instanz aus."
|
|
: err.message);
|
|
listeContainer.replaceChildren(
|
|
erstelleElement("div", { klasse: "fehler-zentriert", text })
|
|
);
|
|
}
|
|
}
|
|
|
|
async function rendereDetails(agentId) {
|
|
gewaehlterAgentId = agentId;
|
|
const detailContainer = document.querySelector(".agenten-detail-bereich");
|
|
if (!detailContainer) return;
|
|
detailContainer.replaceChildren();
|
|
|
|
if (!agentId) {
|
|
detailContainer.append(
|
|
erstelleKarte("Agent-Details", [
|
|
erstelleElement("p", { klasse: "gedaempft", text: "Wähle links einen Agenten aus." })
|
|
])
|
|
);
|
|
return;
|
|
}
|
|
|
|
const agent = agenten.find((a) => a.agentId === agentId);
|
|
if (!agent) return;
|
|
|
|
// Kopf-Aktionen (Chatten, Löschen)
|
|
const aktionen = erstelleElement("div", { klasse: "gruppe-horizontal" });
|
|
|
|
const zumChatBtn = erstelleElement(
|
|
"button",
|
|
{
|
|
klasse: "schaltflaeche flach",
|
|
title: "Mit diesem Agenten chatten",
|
|
onclick: () => {
|
|
window.location.hash = `#/chat/${encodeURIComponent(agent.agentId)}`;
|
|
}
|
|
},
|
|
erstelleIcon(PFADE.chat, 14),
|
|
erstelleElement("span", { text: " Chatten" })
|
|
);
|
|
|
|
const loeschenBtn = erstelleElement(
|
|
"button",
|
|
{
|
|
klasse: "schaltflaeche gefahr flach",
|
|
title: "Diesen Agenten löschen",
|
|
onclick: () => loescheAgent(agent.agentId, agent.displayName)
|
|
},
|
|
erstelleIcon(PFADE.muelleimer, 14),
|
|
erstelleElement("span", { text: " Löschen" })
|
|
);
|
|
|
|
aktionen.append(zumChatBtn, loeschenBtn);
|
|
|
|
// Stammdaten-Karte
|
|
const stammdatenListe = erstelleElement("dl", { klasse: "datenliste" });
|
|
const felder = [
|
|
["Name", agent.displayName],
|
|
["Kennung (ID)", agent.agentId],
|
|
["Modell", agent.model],
|
|
["Beschreibung", agent.description || "—"]
|
|
];
|
|
|
|
for (const [bez, wert] of felder) {
|
|
stammdatenListe.append(
|
|
erstelleElement("dt", { text: bez }),
|
|
erstelleElement("dd", { text: wert })
|
|
);
|
|
}
|
|
|
|
const basisKarte = erstelleKarte(agent.displayName, [stammdatenListe], { aktionen });
|
|
|
|
// Werkzeuge-Karte
|
|
const werkzeugeListe = erstelleElement("div", { klasse: "werkzeuge-liste" });
|
|
|
|
if (!agent.tools || agent.tools.length === 0) {
|
|
werkzeugeListe.append(
|
|
erstelleElement("p", { klasse: "gedaempft", text: "Keine Werkzeuge für diesen Agenten konfiguriert." })
|
|
);
|
|
} else {
|
|
for (const tool of agent.tools) {
|
|
const zeile = erstelleElement("div", { klasse: "werkzeug-zeile" });
|
|
|
|
const links = erstelleElement("div", { klasse: "werkzeug-info" });
|
|
const toolTitel = erstelleElement("strong", { text: tool.name });
|
|
const toolDesc = erstelleElement("div", {
|
|
klasse: "werkzeug-beschreibung gedaempft",
|
|
text: tool.description || ""
|
|
});
|
|
links.append(toolTitel, toolDesc);
|
|
|
|
const rechts = erstelleElement("div", { klasse: "werkzeug-aktionen" });
|
|
|
|
const einstellungenBtn = erstelleElement(
|
|
"button",
|
|
{
|
|
klasse: "schaltflaeche flach klein",
|
|
title: "Werkzeug-Einstellungen einsehen",
|
|
onclick: () => zeigeWerkzeugEinstellungen(agent.agentId, tool.name)
|
|
},
|
|
erstelleIcon(PFADE.einstellungen, 14),
|
|
erstelleElement("span", { text: " Konfiguration" })
|
|
);
|
|
|
|
const toggleLabel = erstelleElement("label", { klasse: "schalter" });
|
|
const toggleInput = erstelleElement("input", {
|
|
type: "checkbox",
|
|
checked: Boolean(tool.enabled),
|
|
onchange: async (e) => {
|
|
const aktiv = e.target.checked;
|
|
try {
|
|
await api(
|
|
`/agents/${encodeURIComponent(agent.agentId)}/tools/${encodeURIComponent(tool.name)}?enabled=${aktiv}`,
|
|
{ method: "PUT" }
|
|
);
|
|
tool.enabled = aktiv;
|
|
zeigeMeldung(`Werkzeug '${tool.name}' ${aktiv ? "aktiviert" : "deaktiviert"}.`, "info");
|
|
} catch (err) {
|
|
e.target.checked = !aktiv;
|
|
zeigeMeldung(`Änderung fehlgeschlagen: ${err.message}`, "gefahr");
|
|
}
|
|
}
|
|
});
|
|
const toggleSpan = erstelleElement("span", { klasse: "schalter-regler" });
|
|
toggleLabel.append(toggleInput, toggleSpan);
|
|
|
|
rechts.append(einstellungenBtn, toggleLabel);
|
|
zeile.append(links, rechts);
|
|
werkzeugeListe.append(zeile);
|
|
}
|
|
}
|
|
|
|
const werkzeugeKarte = erstelleKarte("Verfügbare Werkzeuge", [werkzeugeListe]);
|
|
|
|
detailContainer.append(basisKarte, werkzeugeKarte);
|
|
}
|
|
|
|
function oeffneNeuerAgentDialog() {
|
|
const formular = erstelleElement("form", {
|
|
klasse: "formular-raster",
|
|
id: "neuer-agent-formular"
|
|
});
|
|
|
|
const idFeld = erstelleElement("input", {
|
|
type: "text",
|
|
required: true,
|
|
placeholder: "z.B. scout",
|
|
id: "neu-agent-id"
|
|
});
|
|
|
|
const nameFeld = erstelleElement("input", {
|
|
type: "text",
|
|
required: true,
|
|
placeholder: "z.B. Scout Agent",
|
|
id: "neu-agent-name"
|
|
});
|
|
|
|
const modellFeld = erstelleElement("input", {
|
|
type: "text",
|
|
required: true,
|
|
placeholder: "z.B. anthropic/claude-3.5-sonnet",
|
|
value: "anthropic/claude-3.5-sonnet",
|
|
id: "neu-agent-modell"
|
|
});
|
|
|
|
const descFeld = erstelleElement("textarea", {
|
|
rows: "3",
|
|
placeholder: "Optionale Beschreibung des Agenten…",
|
|
id: "neu-agent-desc"
|
|
});
|
|
|
|
formular.append(
|
|
erstelleElement("label", { text: "Agent-ID (eindeutiger Schlüssel):" }),
|
|
idFeld,
|
|
erstelleElement("label", { text: "Anzeigename:" }),
|
|
nameFeld,
|
|
erstelleElement("label", { text: "Modell (OpenRouter):" }),
|
|
modellFeld,
|
|
erstelleElement("label", { text: "Beschreibung:" }),
|
|
descFeld
|
|
);
|
|
|
|
const abbrechenBtn = erstelleElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
klasse: "schaltflaeche flach",
|
|
onclick: () => modal.schliessen()
|
|
},
|
|
erstelleElement("span", { text: "Abbrechen" })
|
|
);
|
|
|
|
const speichernBtn = erstelleElement(
|
|
"button",
|
|
{
|
|
type: "submit",
|
|
klasse: "schaltflaeche primaer",
|
|
form: "neuer-agent-formular",
|
|
onclick: () => {
|
|
formular.requestSubmit();
|
|
}
|
|
},
|
|
erstelleElement("span", { text: "Agent anlegen" })
|
|
);
|
|
|
|
const modal = erstelleModal("Neuen Agenten anlegen", formular, [abbrechenBtn, speichernBtn]);
|
|
|
|
formular.addEventListener("submit", async (e) => {
|
|
e.preventDefault();
|
|
speichernBtn.disabled = true;
|
|
|
|
try {
|
|
await api("/agents", {
|
|
method: "POST",
|
|
body: {
|
|
agentId: idFeld.value.trim(),
|
|
displayName: nameFeld.value.trim(),
|
|
model: modellFeld.value.trim(),
|
|
description: descFeld.value.trim()
|
|
}
|
|
});
|
|
modal.schliessen();
|
|
zeigeMeldung(`Agent '${nameFeld.value.trim()}' wurde erfolgreich angelegt.`, "erfolg");
|
|
await ladeUndRendereListe(idFeld.value.trim());
|
|
} catch (err) {
|
|
zeigeMeldung(`Fehler beim Anlegen: ${err.message}`, "gefahr");
|
|
} finally {
|
|
speichernBtn.disabled = false;
|
|
}
|
|
});
|
|
|
|
modal.oeffnen();
|
|
}
|
|
|
|
async function loescheAgent(agentId, name) {
|
|
if (!confirm(`Soll der Agent '${name}' (${agentId}) wirklich gelöscht werden?`)) return;
|
|
|
|
try {
|
|
await api(`/agents/${encodeURIComponent(agentId)}`, { method: "DELETE" });
|
|
zeigeMeldung(`Agent '${name}' wurde gelöscht.`, "info");
|
|
await ladeUndRendereListe();
|
|
} catch (err) {
|
|
zeigeMeldung(`Löschen fehlgeschlagen: ${err.message}`, "gefahr");
|
|
}
|
|
}
|
|
|
|
async function zeigeWerkzeugEinstellungen(agentId, toolName) {
|
|
const box = erstelleElement("div", { klasse: "einstellungen-box" });
|
|
box.append(erstelleElement("p", { klasse: "gedaempft", text: "Lade Konfiguration…" }));
|
|
|
|
const schliessenBtn = erstelleElement("button", {
|
|
type: "button",
|
|
klasse: "schaltflaeche flach",
|
|
onclick: () => modal.schliessen(),
|
|
text: "Schließen"
|
|
});
|
|
|
|
const modal = erstelleModal(
|
|
`Einstellungen: ${toolName} (${agentId})`,
|
|
box,
|
|
[schliessenBtn]
|
|
);
|
|
modal.oeffnen();
|
|
|
|
try {
|
|
const daten = await api(
|
|
`/agents/${encodeURIComponent(agentId)}/tools/${encodeURIComponent(toolName)}/settings`
|
|
);
|
|
box.replaceChildren();
|
|
|
|
const hinweis = erstelleElement("p", {
|
|
klasse: "hinweis",
|
|
text: "Geheime Werte (Passwörter, Schlüssel) werden vom Server maskiert ausgegeben."
|
|
});
|
|
box.append(hinweis);
|
|
|
|
if (!daten || Object.keys(daten).length === 0) {
|
|
box.append(
|
|
erstelleElement("p", { klasse: "gedaempft", text: "Keine individuellen Einstellungen hinterlegt." })
|
|
);
|
|
return;
|
|
}
|
|
|
|
const dl = erstelleElement("dl", { klasse: "datenliste" });
|
|
for (const [schluessel, wert] of Object.entries(daten)) {
|
|
const dt = erstelleElement("dt", { text: schluessel });
|
|
const dd = erstelleElement("dd", { text: typeof wert === "object" ? JSON.stringify(wert) : String(wert) });
|
|
dl.append(dt, dd);
|
|
}
|
|
box.append(dl);
|
|
} catch (err) {
|
|
box.replaceChildren(
|
|
erstelleElement("p", { klasse: "fehler", text: `Laden fehlgeschlagen: ${err.message}` })
|
|
);
|
|
}
|
|
}
|