Verweis statt Nacherzaehlung: die Engine stempelt den Ursprung (#697, #677)

- Werkzeugkontext kennt Lauf und Aufgabe des Laufs (RunId, RunTaskId); die
  Delegationskette traegt den Lauf des Aufrufers weiter (RunId, CallerRunId)
- Aufgaben tragen origin_run / origin_task (Datei, DB, Taskboard "Arose from"),
  gestempelt, nie vom Agenten behauptet
- Kommentare nennen den Lauf, in dem sie geschrieben wurden
- Laufbeleg: CallerRunId; Laufansicht fuehrt zu "Delegiert von" und "Delegiert an"
- #677: task_update ersetzt den Rumpf nicht mehr, Abnahmekriterien aendert nur der
  Ersteller; Kommentare der Fremdinhalt-Stufe auf fremden Aufgaben kommen gerahmt an
- WebUI: Herkunft auf der Aufgabenkarte mit Link zum Lauf

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-25 09:27:47 +02:00
co-authored by Claude Opus 5.5
parent 3f90e0d0cd
commit f812dbd856
26 changed files with 493 additions and 47 deletions
@@ -359,6 +359,9 @@
"tasks.boardTopLevel": "Oberste Ebene",
"tasks.parent": "Dient Aufgabe (Eltern)",
"tasks.serves": "dient {id}",
"tasks.origin": "Herkunft",
"tasks.originTask": "bei der Arbeit an {id}",
"tasks.originRun": "Lauf öffnen",
"services.title": "Dienste",
"services.newService": "Neuer Dienst",
@@ -746,6 +749,8 @@
"detail.toolCalls": "Werkzeugaufrufe",
"detail.toolCalls.hint": "Aus dem Audit-Log — auch ohne Verlauf vorhanden.",
"detail.approvals": "Zur Freigabe vorgelegt",
"detail.calledBy": "Delegiert von",
"detail.calledRuns": "Delegiert an",
"timeline.title": "Tokennutzung › Zeitverlauf",
"timeline.chart": "Zeitverlauf",
@@ -359,6 +359,9 @@
"tasks.boardTopLevel": "Top level",
"tasks.parent": "Serves task (parent)",
"tasks.serves": "serves {id}",
"tasks.origin": "Arose from",
"tasks.originTask": "while working on {id}",
"tasks.originRun": "open run",
"services.title": "Services",
"services.newService": "New Service",
@@ -746,6 +749,8 @@
"detail.toolCalls": "Tool calls",
"detail.toolCalls.hint": "From the audit log - available even without a transcript.",
"detail.approvals": "Proposed for approval",
"detail.calledBy": "Delegated from",
"detail.calledRuns": "Delegated to",
"timeline.title": "Token usage › Timeline",
"timeline.chart": "Timeline",
@@ -78,10 +78,26 @@ function buildReceipt(run) {
add("runs.col.cost", r.costIsKnown || r.costUsd > 0 ? formatiereKosten(r.costUsd) : "—");
add("runs.col.duration", formatDuration(r.durationMs));
if (r.result) add("detail.result", r.result);
// #697: the way back to the run that asked for this one.
if (r.callerRunId) add("detail.calledBy", laufLink(r.callerRunId));
}
// #697: and the way down to the runs this one asked.
if (run.calledRuns && run.calledRuns.length > 0) {
const liste = erstelleElement("span", {});
run.calledRuns.forEach((c, i) => {
if (i > 0) liste.append(", ");
liste.append(laufLink(c.runId, `${c.agentId} · ${c.runId.slice(0, 8)}`));
});
add("detail.calledRuns", liste);
}
return erstelleKarte(t("detail.receipt"), [list]);
}
/** #697: A run, linked — its content stays where it is, the reference travels. */
export function laufLink(runId, text) {
return erstelleElement("a", { href: `#/verbrauch/runs/${encodeURIComponent(runId)}`, text: text || runId });
}
function buildTranscriptState(run) {
const text = {
disabled: t("detail.transcript.disabled"),
@@ -9,6 +9,7 @@
import { api } from "../modules/api.js";
import { t } from "../modules/i18n.js";
import { laufLink } from "./run-detail.js";
import {
erstelleElement,
erstelleIcon,
@@ -202,6 +203,19 @@ function erstelleAufgabenKarte(task) {
})
);
// #697: Where the task came from — stamped by the engine, with the way back to the run.
if (task.originRunId || task.originTaskId) {
const herkunft = erstelleElement("p", { klasse: "gedaempft text-klein" }, `${t("tasks.origin")}: `);
const teile = [];
if (task.createdBy) teile.push(task.createdBy);
if (task.originTaskId) teile.push(t("tasks.originTask", { id: task.originTaskId }));
herkunft.append(teile.join(", "));
if (task.originRunId) {
herkunft.append(teile.length > 0 ? " · " : "", laufLink(task.originRunId, t("tasks.originRun")));
}
zeilen.push(herkunft);
}
const letzter = erstelleElement("p", { klasse: "gedaempft text-klein" }, `${t("tasks.lastRun")}: `);
if (task.lastRun) {
letzter.append(