#643: Entscheidungen wecken den Agenten nur noch bei Ablehnung oder gescheiterter Ausfuehrung, gesammelt in einer Aufgabe je Agent (5 min Ruhefenster), frisch per @new:agent statt im fortgefuehrten Chatverlauf. Eine einmalige Aufgabe, deren Lauf wegen Budget oder Herunterfahren gar nicht stattfand, bleibt faellig (Wiedervorlage nach 30 min) statt mit gesetztem Marker liegenzubleiben. #641: Rechte stehen in der Werkzeugkonfiguration des Agenten (erlaubt/verboten). Erlaubtes laeuft, Nicht-Erlaubtes wird einzeln vorgelegt statt verweigert, Verbotenes gar nicht erst. "Gleichartiges kuenftig erlauben" erteilt eine widerrufbare Dauerfreigabe je Agent/Werkzeug/Operation; nach drei gleichartigen Freigaben schlaegt die Oberflaeche sie vor. Aenderungen an Agenten bleiben ausnahmslos freigabepflichtig. Database: Operation ist das SQL-Schluesselwort; die eigene Zugriffspruefung greift nur noch ohne Freigabestelle (AgentToolContext.FreigabeGeprueft). WebUI: Rechte-Tabelle je Werkzeug, Dauerfreigaben mit Widerruf. Version 0.1.11. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1375,3 +1375,43 @@ button.klein {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
|
||||
/* ─── Hinweiskaesten (#608, #641) ─── */
|
||||
|
||||
.hinweis-box {
|
||||
border: 1px solid var(--divider);
|
||||
border-left-width: 4px;
|
||||
background: var(--surface);
|
||||
padding: 10px 14px;
|
||||
margin: 10px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.hinweis-box.warnung {
|
||||
border-color: var(--warnung);
|
||||
background: rgba(181, 130, 42, 0.10);
|
||||
}
|
||||
|
||||
.hinweis-box.vorschlag {
|
||||
border-color: var(--erfolg);
|
||||
background: rgba(63, 143, 92, 0.10);
|
||||
}
|
||||
|
||||
/* ─── Rechte je Werkzeug (#641) ─── */
|
||||
|
||||
.rechte-tabelle select {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.rechte-tabelle .grund {
|
||||
font-size: 12px;
|
||||
color: var(--gedaempft);
|
||||
}
|
||||
|
||||
.gleichartig-wahl {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@@ -411,19 +411,129 @@ async function zeigeWerkzeugEinstellungen(agentId, toolName) {
|
||||
box.append(
|
||||
erstelleElement("p", { klasse: "gedaempft", text: "Keine individuellen Einstellungen hinterlegt." })
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
|
||||
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}` })
|
||||
);
|
||||
}
|
||||
|
||||
const rechteBox = erstelleElement("div", { klasse: "einstellungen-box" });
|
||||
box.after(rechteBox);
|
||||
await zeigeWerkzeugrechte(agentId, toolName, rechteBox);
|
||||
}
|
||||
|
||||
const WIRKUNG = {
|
||||
auto: ["läuft", "erfolg"],
|
||||
approve: ["einzeln freigeben", "warnung"],
|
||||
deny: ["gesperrt", "gefahr"]
|
||||
};
|
||||
|
||||
/**
|
||||
* #641: Rechte je Operation. "erlaubt" laeuft ohne Rueckfrage, "freigabe" wird einzeln
|
||||
* vorgelegt, "verboten" gar nicht erst. Die Wirkung zeigt, was tatsaechlich geschieht -
|
||||
* auch durch Dauerfreigaben oder die Vorgabe des Werkzeugs.
|
||||
*/
|
||||
async function zeigeWerkzeugrechte(agentId, toolName, box) {
|
||||
const pfad = `/agents/${encodeURIComponent(agentId)}/tools/${encodeURIComponent(toolName)}/rights`;
|
||||
let rechte;
|
||||
try {
|
||||
rechte = await api(pfad);
|
||||
} catch (err) {
|
||||
box.replaceChildren(erstelleElement("p", { klasse: "gedaempft", text: `Rechte nicht lesbar: ${err.message}` }));
|
||||
return;
|
||||
}
|
||||
|
||||
box.replaceChildren(erstelleElement("h3", { text: "Rechte" }));
|
||||
|
||||
if (!rechte.operations || rechte.operations.length === 0) {
|
||||
box.append(
|
||||
erstelleElement("p", {
|
||||
klasse: "gedaempft",
|
||||
text: "Dieses Werkzeug kennt keine einzelnen Operationen. Es gilt die Standardregel."
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
box.append(
|
||||
erstelleElement("p", {
|
||||
klasse: "hinweis",
|
||||
text: rechte.configured
|
||||
? "Erlaubte Operationen laufen ohne Rückfrage, alle anderen werden einzeln zur Freigabe vorgelegt, verbotene gar nicht erst."
|
||||
: "Noch keine Rechte gesetzt: Es gilt die Vorgabe des Werkzeugs. Mit dem Speichern werden die Einstellungen unten verbindlich."
|
||||
})
|
||||
);
|
||||
|
||||
const tabelle = erstelleElement("table", { klasse: "daten-tabelle rechte-tabelle" });
|
||||
const kopf = erstelleElement("tr", {});
|
||||
for (const titel of ["Operation", "Einstellung", "Wirkung"]) {
|
||||
kopf.append(erstelleElement("th", { text: titel }));
|
||||
}
|
||||
tabelle.append(kopf);
|
||||
|
||||
const auswahl = new Map();
|
||||
for (const op of rechte.operations) {
|
||||
const select = erstelleElement(
|
||||
"select",
|
||||
{ disabled: op.alwaysApproval },
|
||||
erstelleElement("option", { value: "erlaubt", text: "erlaubt" }),
|
||||
erstelleElement("option", { value: "freigabe", text: "einzeln freigeben" }),
|
||||
erstelleElement("option", { value: "verboten", text: "verboten" })
|
||||
);
|
||||
// Ohne gesetzte Rechte die tatsaechliche Wirkung vorbelegen - sonst wuerde das erste
|
||||
// Speichern z.B. das freie Lesen still in eine Freigabepflicht verwandeln.
|
||||
select.value = rechte.configured
|
||||
? op.setting
|
||||
: op.effective === "auto" ? "erlaubt" : op.effective === "deny" ? "verboten" : "freigabe";
|
||||
auswahl.set(op.operation, select);
|
||||
|
||||
const [wirkungText, wirkungArt] = WIRKUNG[op.effective] ?? [op.effective, "neutral"];
|
||||
const wirkung = erstelleElement("td", {}, erstellePlakette(wirkungText, wirkungArt));
|
||||
if (op.standingApprovalId) {
|
||||
wirkung.append(" ", erstellePlakette(`Dauerfreigabe #${op.standingApprovalId}`, "neutral"));
|
||||
}
|
||||
wirkung.append(erstelleElement("div", {
|
||||
klasse: "grund",
|
||||
text: op.alwaysApproval ? "Änderungen an Agenten brauchen immer eine Freigabe." : op.reason
|
||||
}));
|
||||
|
||||
const zeile = erstelleElement("tr", {});
|
||||
zeile.append(erstelleElement("td", { text: op.operation }), erstelleElement("td", {}, select), wirkung);
|
||||
tabelle.append(zeile);
|
||||
}
|
||||
box.append(tabelle);
|
||||
|
||||
const speichern = erstelleElement("button", {
|
||||
type: "button",
|
||||
klasse: "schaltflaeche primaer klein",
|
||||
text: "Rechte speichern",
|
||||
onclick: async () => {
|
||||
const allowed = [];
|
||||
const denied = [];
|
||||
for (const [operation, select] of auswahl) {
|
||||
if (select.disabled) continue;
|
||||
if (select.value === "erlaubt") allowed.push(operation);
|
||||
if (select.value === "verboten") denied.push(operation);
|
||||
}
|
||||
speichern.disabled = true;
|
||||
try {
|
||||
const res = await api(pfad, { method: "PUT", body: { allowed, denied } });
|
||||
zeigeMeldung(res?.message ?? "Rechte gespeichert.", "erfolg");
|
||||
await zeigeWerkzeugrechte(agentId, toolName, box);
|
||||
} catch (err) {
|
||||
zeigeMeldung(`Speichern fehlgeschlagen: ${err.message}`, "gefahr");
|
||||
speichern.disabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
box.append(speichern);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ export async function rendereStaging() {
|
||||
);
|
||||
|
||||
const listeBereich = erstelleElement("div", { id: "staging-liste-bereich" });
|
||||
layout.append(kopfZeile, listeBereich);
|
||||
const dauerBereich = erstelleElement("div", { id: "staging-dauer-bereich" });
|
||||
layout.append(kopfZeile, listeBereich, dauerBereich);
|
||||
container.append(layout);
|
||||
|
||||
await ladeFreigaben();
|
||||
@@ -66,6 +67,7 @@ async function ladeFreigaben() {
|
||||
|
||||
// Aktualisiere Badge in Sidebar
|
||||
aktualisiereStagingBadge(wartendeAufrufe.length);
|
||||
ladeDauerfreigaben();
|
||||
|
||||
if (!wartendeAufrufe || wartendeAufrufe.length === 0) {
|
||||
container.append(
|
||||
@@ -82,11 +84,15 @@ async function ladeFreigaben() {
|
||||
for (const anfrage of wartendeAufrufe) {
|
||||
const kopfAktionen = erstelleElement("div", { klasse: "gruppe-horizontal" });
|
||||
|
||||
// #641: "Gleichartiges kuenftig erlauben" - nie vorausgewaehlt, auch nicht beim
|
||||
// Vorschlag. Uebernehmen muss der Mensch.
|
||||
const gleichartig = erstelleElement("input", { type: "checkbox", id: `gleichartig-${anfrage.id}` });
|
||||
|
||||
const freigebenBtn = erstelleElement(
|
||||
"button",
|
||||
{
|
||||
klasse: "schaltflaeche primaer klein",
|
||||
onclick: () => bestaetigeFreigabe(anfrage.id)
|
||||
onclick: () => bestaetigeFreigabe(anfrage.id, gleichartig.checked)
|
||||
},
|
||||
erstelleIcon(PFADE.haken, 14),
|
||||
erstelleElement("span", { text: " Freigeben" })
|
||||
@@ -128,7 +134,36 @@ async function ladeFreigaben() {
|
||||
});
|
||||
argBox.append(pre);
|
||||
|
||||
const karte = erstelleKarte(`${anfrage.tool}.${anfrage.action || "?"}`, [datenliste, argBox], {
|
||||
const teile = [datenliste, argBox];
|
||||
|
||||
if (anfrage.suggestAllowSimilar) {
|
||||
teile.push(
|
||||
erstelleElement("div", {
|
||||
klasse: "hinweis-box vorschlag",
|
||||
text:
|
||||
`Diese Operation von ${anfrage.agentId} wurde in den letzten 14 Tagen schon ` +
|
||||
`${anfrage.previousApprovals}-mal freigegeben. Soll ${anfrage.tool}.${anfrage.action} ` +
|
||||
`für diesen Agenten künftig ohne Rückfrage laufen?`
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (anfrage.canAllowSimilar) {
|
||||
teile.push(
|
||||
erstelleElement(
|
||||
"label",
|
||||
{ klasse: "gleichartig-wahl", for: `gleichartig-${anfrage.id}` },
|
||||
gleichartig,
|
||||
erstelleElement("span", {
|
||||
text:
|
||||
`Gleichartiges künftig erlauben: ${anfrage.agentId} darf ${anfrage.tool}.${anfrage.action} ` +
|
||||
`ab jetzt ohne Rückfrage (widerrufbar unten unter „Dauerfreigaben“).`
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const karte = erstelleKarte(`${anfrage.tool}.${anfrage.action || "?"}`, teile, {
|
||||
aktionen: kopfAktionen
|
||||
});
|
||||
|
||||
@@ -170,16 +205,87 @@ function aktualisiereStagingBadge(anzahl) {
|
||||
}
|
||||
}
|
||||
|
||||
async function bestaetigeFreigabe(id) {
|
||||
async function bestaetigeFreigabe(id, gleichartig = false) {
|
||||
try {
|
||||
const res = await api(`/staging/${id}/approve`, { method: "POST" });
|
||||
zeigeMeldung(`Aufruf freigegeben: ${res?.message ?? "Erfolgreich ausgeführt"}`, "erfolg");
|
||||
const res = await api(`/staging/${id}/approve${gleichartig ? "?similar=true" : ""}`, { method: "POST" });
|
||||
zeigeMeldung(
|
||||
`Aufruf freigegeben${gleichartig ? " und künftig erlaubt" : ""}: ${res?.message ?? "Erfolgreich ausgeführt"}`,
|
||||
"erfolg"
|
||||
);
|
||||
await ladeFreigaben();
|
||||
} catch (err) {
|
||||
zeigeMeldung(`Freigabe fehlgeschlagen: ${err.message}`, "gefahr");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* #641: Die geltenden Dauerfreigaben - "Gleichartiges kuenftig erlauben" - mit Widerruf.
|
||||
*/
|
||||
async function ladeDauerfreigaben() {
|
||||
const bereich = document.getElementById("staging-dauer-bereich");
|
||||
if (!bereich) return;
|
||||
|
||||
let liste;
|
||||
try {
|
||||
liste = await api("/staging/standing");
|
||||
} catch {
|
||||
bereich.replaceChildren();
|
||||
return;
|
||||
}
|
||||
|
||||
const inhalt = [];
|
||||
if (!Array.isArray(liste) || liste.length === 0) {
|
||||
inhalt.push(
|
||||
erstelleElement("p", {
|
||||
klasse: "gedaempft",
|
||||
text:
|
||||
"Keine. Beim Freigeben lässt sich „Gleichartiges künftig erlauben“ wählen; " +
|
||||
"dauerhafte Rechte stehen sonst in der Werkzeugkonfiguration des Agenten."
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const tabelle = erstelleElement("table", { klasse: "daten-tabelle" });
|
||||
const kopf = erstelleElement("tr", {});
|
||||
for (const titel of ["Agent", "Operation", "Erteilt", "Anlass", ""]) {
|
||||
kopf.append(erstelleElement("th", { text: titel }));
|
||||
}
|
||||
tabelle.append(kopf);
|
||||
|
||||
for (const d of liste) {
|
||||
const zeile = erstelleElement("tr", {});
|
||||
const widerrufen = erstelleElement(
|
||||
"button",
|
||||
{ klasse: "schaltflaeche gefahr flach klein", onclick: () => widerrufeDauerfreigabe(d) },
|
||||
erstelleElement("span", { text: "Widerrufen" })
|
||||
);
|
||||
zeile.append(
|
||||
erstelleElement("td", { text: d.agentId }),
|
||||
erstelleElement("td", { text: `${d.tool}.${d.operation}` }),
|
||||
erstelleElement("td", { text: `${formatiereDatumZeit(d.grantedAt)} (${d.grantedBy})` }),
|
||||
erstelleElement("td", { text: d.sourceId ? `#${d.sourceId}` : "—" }),
|
||||
erstelleElement("td", {}, widerrufen)
|
||||
);
|
||||
tabelle.append(zeile);
|
||||
}
|
||||
inhalt.push(tabelle);
|
||||
}
|
||||
|
||||
bereich.replaceChildren(erstelleKarte("Dauerfreigaben", inhalt));
|
||||
}
|
||||
|
||||
async function widerrufeDauerfreigabe(d) {
|
||||
if (!confirm(`Dauerfreigabe widerrufen? ${d.agentId} muss ${d.tool}.${d.operation} dann wieder einzeln vorlegen.`)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await api(`/staging/standing/${d.id}/revoke`, { method: "POST" });
|
||||
zeigeMeldung(res?.message ?? "Widerrufen.", res?.success === false ? "gefahr" : "info");
|
||||
await ladeDauerfreigaben();
|
||||
} catch (err) {
|
||||
zeigeMeldung(`Widerruf fehlgeschlagen: ${err.message}`, "gefahr");
|
||||
}
|
||||
}
|
||||
|
||||
function oeffneAblehnenDialog(id) {
|
||||
const formular = erstelleElement("form", { klasse: "formular-raster" });
|
||||
const grundInput = erstelleElement("input", {
|
||||
|
||||
Reference in New Issue
Block a user