Rueckmeldung nach Vertrauensstufe: drei Wege nach oben (#715)

- IAgentTool.ReadsForeignContent: Werkzeuge mit Aussenkontakt erklaeren sich selbst
  (WebFetch, WebMonitor, DirectAPI, Mail, RocketChat, Telegram, TelegramClient,
  SocialMediaManager, FTP, Database); AgentConfig.TreatAsForeignReader kann nur verschaerfen
- ReportingLine: Stufe aus den Werkzeugen, "wer steht darueber", Pruefung des Wegs nach oben
  (nie Aenderungen an Aufgaben darueber, nur eine Ebene hoch, Fremdinhalt-Stufe gar kein
  Freitext) und Rahmung als untrusted_content
- Engine: Nachricht und Spawn nach oben geprueft, Text der Fremdinhalt-Stufe kommt gerahmt an
- Taskboard: task_create/update/comment, reopen und die Dubletten-Notiz folgen dem Berichtsweg
- Dispatcher: Aufgaben von Agenten der Fremdinhalt-Stufe gehen gerahmt in den Lauf
- TaskReports: Erledigt-Signal und Systemmeldung (in Pruefung, blockiert, abgebrochen) an den
  Auftraggeber, je Zustand einmal, gesammelt in einer offenen Aufgabe je Empfaenger
- OrgBriefing nennt den Berichtsweg; WebUI zeigt Stufe und Schalter am Agenten

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-25 09:16:29 +02:00
co-authored by Claude Opus 5.5
parent e2ffe4b380
commit 3857a7cab2
33 changed files with 1234 additions and 30 deletions
@@ -781,6 +781,19 @@ button.klein {
gap: 12px;
}
/* #715: marks a tool that brings outside text in — it changes the agent's reporting line. */
.werkzeug-fremd {
display: inline-block;
margin-left: 6px;
padding: 0 6px;
border: 1px solid var(--warnung);
color: var(--warnung);
font-size: 11px;
font-weight: 400;
letter-spacing: 0.02em;
vertical-align: middle;
}
/* ─── Ansicht: Aufgaben & Jobs ─── */
.reiter-leiste {
@@ -319,6 +319,14 @@
"agents.effectApprove": "einzeln freigeben",
"agents.effectDeny": "gesperrt",
"agents.standingApproval": "Dauerfreigabe #{id}",
"agents.readsForeign": "liest fremde Inhalte",
"agents.reportingLine": "Berichtsweg",
"agents.tierStandard": "Standardstufe. Darf die Aufgaben der Leitung kommentieren, aber nicht ändern. Ist eine Aufgabe erledigt, die die Leitung vergeben hat, erfährt die Leitung davon.",
"agents.tierForeign": "Liest fremde Inhalte ({sources}). Schreibt keinen Freitext an die Leitung, sondern meldet über die eigenen Aufgaben (erledigt, blockiert oder abgebrochen), und das System benachrichtigt die Leitung. Was der Agent an Kollegen schreibt, kommt dort als Daten gekennzeichnet an.",
"agents.settingSource": "Einstellung",
"agents.treatAsForeignReader": "Immer als Leser fremder Inhalte behandeln",
"agents.treatAsForeignReaderHint": "Kann nur verschärfen. Die Stufe folgt aus den Werkzeugen. Diese Einstellung setzt sie für einen Agenten, dessen Eingaben auf anderem Weg von außen kommen.",
"agents.foreignReaderSaved": "Berichtsweg von {name} gespeichert.",
"tasks.title": "Aufgaben",
"tasks.newTask": "Neue Aufgabe",
@@ -319,6 +319,14 @@
"agents.effectApprove": "single approval",
"agents.effectDeny": "denied",
"agents.standingApproval": "Standing approval #{id}",
"agents.readsForeign": "reads outside content",
"agents.reportingLine": "Reporting line",
"agents.tierStandard": "Standard tier. May comment on its lead's tasks, but not change them. When it finishes a task its lead gave it, the lead is told.",
"agents.tierForeign": "Reads outside content ({sources}). Writes no free text to its lead: it reports through its own tasks (done, blocked or canceled), and the system tells the lead. What it writes to colleagues arrives marked as data.",
"agents.settingSource": "setting",
"agents.treatAsForeignReader": "Always treat as reading outside content",
"agents.treatAsForeignReaderHint": "Can only tighten. The tools decide the tier; this adds it for an agent whose input comes from outside some other way.",
"agents.foreignReaderSaved": "Reporting line of {name} saved.",
"tasks.title": "Tasks",
"tasks.newTask": "New Task",
@@ -217,6 +217,13 @@ async function rendereDetails(agentId) {
const links = erstelleElement("div", { klasse: "werkzeug-info" });
const toolTitel = erstelleElement("strong", { text: tool.name });
// #715: A tool that brings in outside text changes the agent's reporting line.
if (tool.readsForeignContent) {
toolTitel.append(
" ",
erstelleElement("span", { klasse: "werkzeug-fremd", text: t("agents.readsForeign") })
);
}
const toolDesc = erstelleElement("div", {
klasse: "werkzeug-beschreibung gedaempft",
text: tool.description || ""
@@ -248,6 +255,7 @@ async function rendereDetails(agentId) {
{ method: "PUT" }
);
tool.enabled = aktiv;
aktualisiereBerichtsweg();
zeigeMeldung(
t("agents.toolToggled", {
tool: tool.name,
@@ -272,7 +280,56 @@ async function rendereDetails(agentId) {
const werkzeugeKarte = erstelleKarte(t("agents.availableTools"), [werkzeugeListe]);
detailContainer.append(basisKarte, werkzeugeKarte);
// #715: The reporting line — the tier follows from the tools; the setting can only tighten it.
const berichtswegText = erstelleElement("p", { klasse: "gedaempft" });
const aktualisiereBerichtsweg = () => {
const quellen = (agent.tools || [])
.filter((w) => w.enabled && w.readsForeignContent)
.map((w) => w.name);
if (agent.treatAsForeignReader) quellen.push(t("agents.settingSource"));
berichtswegText.textContent = quellen.length > 0
? t("agents.tierForeign", { sources: quellen.join(", ") })
: t("agents.tierStandard");
};
aktualisiereBerichtsweg();
const verschaerfenLabel = erstelleElement("label", { klasse: "schalter" });
const verschaerfenInput = erstelleElement("input", {
type: "checkbox",
checked: Boolean(agent.treatAsForeignReader),
onchange: async (e) => {
const wert = e.target.checked;
try {
await api(
`/agents/${encodeURIComponent(agent.agentId)}/foreign-reader?value=${wert}`,
{ method: "PUT" }
);
agent.treatAsForeignReader = wert;
aktualisiereBerichtsweg();
zeigeMeldung(t("agents.foreignReaderSaved", { name: agent.displayName }), "info");
} catch (err) {
e.target.checked = !wert;
zeigeMeldung(t("agents.saveFailed", { error: err.message }), "gefahr");
}
}
});
verschaerfenLabel.append(verschaerfenInput, erstelleElement("span", { klasse: "schalter-regler" }));
const verschaerfenZeile = erstelleElement(
"div",
{ klasse: "werkzeug-zeile" },
erstelleElement(
"div",
{ klasse: "werkzeug-info" },
erstelleElement("strong", { text: t("agents.treatAsForeignReader") }),
erstelleElement("div", { klasse: "werkzeug-beschreibung gedaempft", text: t("agents.treatAsForeignReaderHint") })
),
erstelleElement("div", { klasse: "werkzeug-aktionen" }, verschaerfenLabel)
);
const berichtswegKarte = erstelleKarte(t("agents.reportingLine"), [berichtswegText, verschaerfenZeile]);
detailContainer.append(basisKarte, werkzeugeKarte, berichtswegKarte);
}
function oeffneNeuerAgentDialog() {