Read workflow-status telemetry from CLI transcript files
Dynamic ultracode workflows run as a detached background task with their own transcript dir, so their subagent events never flow through the main -p stream. The previous parent_tool_use_id tap therefore captured nothing for real workflows and the status stayed "none" the whole run. Replace the tap with a file-based reader: - TapWorkflowLaunch parses the "Transcript dir:" line from the Workflow tool_result and registers the run. - WorkflowProgressTracker becomes a per-session registry of transcript paths (no longer a stream accumulator). - WorkflowTelemetryReader reads journal.jsonl for live counts (running) and <runId>.json for rich per-agent telemetry (completed); FinalizeChain persists the rich JSON into chains.workflow_info. - Modal renders phases + a per-agent list (label/phase/state/tokens/ tool-calls/duration) instead of the obsolete tool breakdown. Sync ARCHITECTURE.md, ARCHITECTURE_QUICK.md and README.md to the new mechanism. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
b6e546f618
commit
2b95161596
9 files changed
+453
-175
No files matched your search
+90
-8
@@ -1025,9 +1025,27 @@
|
||||
opacity: var(--semi-transparent);
|
||||
}
|
||||
|
||||
.wf-title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .15em;
|
||||
margin-bottom: var(--box-spacing);
|
||||
}
|
||||
|
||||
.wf-name {
|
||||
font-family: var(--mono);
|
||||
color: var(--text-accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.wf-summary {
|
||||
font-size: .9em;
|
||||
opacity: var(--semi-transparent);
|
||||
}
|
||||
|
||||
.wf-stat-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));
|
||||
gap: var(--badge-spacing);
|
||||
}
|
||||
|
||||
@@ -1060,27 +1078,91 @@
|
||||
margin-bottom: var(--badge-spacing);
|
||||
}
|
||||
|
||||
.wf-tool-list {
|
||||
/* Phasen-Liste */
|
||||
.wf-phase-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--border-width);
|
||||
}
|
||||
|
||||
.wf-tool-row {
|
||||
.wf-phase-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
gap: var(--badge-spacing);
|
||||
padding: .35em var(--badge-spacing);
|
||||
background: var(--f3-bg);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.wf-tool-name {
|
||||
font-family: var(--mono);
|
||||
.wf-phase-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.wf-tool-count {
|
||||
color: var(--primary);
|
||||
.wf-phase-detail {
|
||||
font-size: .85em;
|
||||
opacity: var(--semi-transparent);
|
||||
}
|
||||
|
||||
/* Subagenten-Liste */
|
||||
.wf-agent-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--border-width);
|
||||
}
|
||||
|
||||
.wf-agent-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--badge-spacing);
|
||||
padding: .4em var(--badge-spacing);
|
||||
background: var(--f3-bg);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.wf-agent-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--badge-spacing);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wf-agent-state {
|
||||
width: .6em;
|
||||
height: .6em;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
background: var(--text);
|
||||
}
|
||||
|
||||
.wf-agent-state.wf-ag-done { background: var(--secondary); }
|
||||
.wf-agent-state.wf-ag-running { background: var(--info); }
|
||||
.wf-agent-state.wf-ag-other { background: var(--primary); opacity: var(--semi-transparent); }
|
||||
|
||||
.wf-agent-label {
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.wf-agent-phase {
|
||||
font-size: .8em;
|
||||
opacity: var(--semi-transparent);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wf-agent-metrics {
|
||||
display: flex;
|
||||
gap: var(--box-spacing);
|
||||
font-family: var(--mono);
|
||||
font-size: .82em;
|
||||
opacity: var(--semi-transparent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.wf-agent-metrics span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wf-meta {
|
||||
|
||||
+68
-20
@@ -100,6 +100,14 @@ async function showPayloadPreviewModal(sessionId) {
|
||||
|
||||
function _wfNum(n) { return Number(n || 0).toLocaleString('de-DE'); }
|
||||
function _wfTime(t) { try { return new Date(t).toLocaleTimeString('de-DE'); } catch (e) { return ''; } }
|
||||
function _wfDur(ms) {
|
||||
ms = Number(ms || 0);
|
||||
if (ms <= 0) return '–';
|
||||
var s = Math.round(ms / 1000);
|
||||
if (s < 60) return s + 's';
|
||||
var m = Math.floor(s / 60);
|
||||
return m + 'm ' + (s % 60) + 's';
|
||||
}
|
||||
|
||||
function _renderWorkflowStatus(body, data) {
|
||||
var status = (data && data.status) || 'none';
|
||||
@@ -127,11 +135,22 @@ function _renderWorkflowStatus(body, data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Name / Zusammenfassung des Workflows (nur nach Abschluss vorhanden).
|
||||
if (wf.workflowName || wf.summary) {
|
||||
var title = document.createElement('div');
|
||||
title.className = 'wf-title';
|
||||
title.innerHTML = (wf.workflowName ? '<span class="wf-name">' + esc(wf.workflowName) + '</span>' : '') +
|
||||
(wf.summary ? '<span class="wf-summary">' + esc(wf.summary) + '</span>' : '');
|
||||
body.appendChild(title);
|
||||
}
|
||||
|
||||
// Kennzahlen.
|
||||
var stats = [
|
||||
{ label: 'Subagenten', value: _wfNum(wf.subagentCount) },
|
||||
{ label: 'Tool-Aufrufe', value: _wfNum(wf.subagentToolCalls) },
|
||||
{ label: 'Input-Tokens', value: _wfNum(wf.subagentInputTokens) },
|
||||
{ label: 'Output-Tokens', value: _wfNum(wf.subagentOutputTokens) }
|
||||
{ label: 'Fertig', value: _wfNum(wf.completedCount) + ' / ' + _wfNum(wf.subagentCount) },
|
||||
{ label: 'Tokens', value: _wfNum(wf.totalTokens) },
|
||||
{ label: 'Tool-Aufrufe', value: _wfNum(wf.totalToolCalls) },
|
||||
{ label: status === 'running' ? 'Läuft seit' : 'Dauer', value: _wfDur(wf.durationMs) }
|
||||
];
|
||||
var grid = document.createElement('div');
|
||||
grid.className = 'wf-stat-grid';
|
||||
@@ -144,30 +163,59 @@ function _renderWorkflowStatus(body, data) {
|
||||
});
|
||||
body.appendChild(grid);
|
||||
|
||||
var tb = wf.toolBreakdown || {};
|
||||
var keys = Object.keys(tb).sort(function (a, b) { return tb[b] - tb[a]; });
|
||||
if (keys.length) {
|
||||
var sec = document.createElement('div');
|
||||
sec.className = 'wf-section';
|
||||
sec.innerHTML = '<div class="wf-section-lbl">Tool-Aufschlüsselung</div>';
|
||||
var list = document.createElement('div');
|
||||
list.className = 'wf-tool-list';
|
||||
keys.forEach(function (k) {
|
||||
// Phasen (nur nach Abschluss).
|
||||
var phases = wf.phases || [];
|
||||
if (phases.length) {
|
||||
var psec = document.createElement('div');
|
||||
psec.className = 'wf-section';
|
||||
psec.innerHTML = '<div class="wf-section-lbl">Phasen</div>';
|
||||
var plist = document.createElement('div');
|
||||
plist.className = 'wf-phase-list';
|
||||
phases.forEach(function (p) {
|
||||
var row = document.createElement('div');
|
||||
row.className = 'wf-tool-row';
|
||||
row.innerHTML = '<span class="wf-tool-name">' + esc(k) +
|
||||
'</span><span class="wf-tool-count">' + esc(String(tb[k])) + '</span>';
|
||||
list.appendChild(row);
|
||||
row.className = 'wf-phase-row';
|
||||
row.innerHTML = '<span class="wf-phase-title">' + esc(p.title || '') + '</span>' +
|
||||
(p.detail ? '<span class="wf-phase-detail">' + esc(p.detail) + '</span>' : '');
|
||||
plist.appendChild(row);
|
||||
});
|
||||
sec.appendChild(list);
|
||||
body.appendChild(sec);
|
||||
psec.appendChild(plist);
|
||||
body.appendChild(psec);
|
||||
}
|
||||
|
||||
// Subagenten-Details (nur nach Abschluss; live liegen noch keine Labels vor).
|
||||
var agents = wf.agents || [];
|
||||
if (agents.length) {
|
||||
var asec = document.createElement('div');
|
||||
asec.className = 'wf-section';
|
||||
asec.innerHTML = '<div class="wf-section-lbl">Subagenten</div>';
|
||||
var alist = document.createElement('div');
|
||||
alist.className = 'wf-agent-list';
|
||||
agents.forEach(function (a) {
|
||||
var st = a.state || '';
|
||||
var stCls = st === 'done' ? 'wf-ag-done' : (st === 'running' ? 'wf-ag-running' : 'wf-ag-other');
|
||||
var row = document.createElement('div');
|
||||
row.className = 'wf-agent-row';
|
||||
row.innerHTML =
|
||||
'<div class="wf-agent-main">' +
|
||||
'<span class="wf-agent-state ' + stCls + '"></span>' +
|
||||
'<span class="wf-agent-label">' + esc(a.label || ('Agent ' + (a.index || ''))) + '</span>' +
|
||||
(a.phaseTitle ? '<span class="wf-agent-phase">' + esc(a.phaseTitle) + '</span>' : '') +
|
||||
'</div>' +
|
||||
'<div class="wf-agent-metrics">' +
|
||||
'<span title="Tokens"><i class="fa fa-database"></i> ' + _wfNum(a.tokens) + '</span>' +
|
||||
'<span title="Tool-Aufrufe"><i class="fa fa-wrench"></i> ' + _wfNum(a.toolCalls) + '</span>' +
|
||||
'<span title="Dauer"><i class="fa fa-clock-o"></i> ' + _wfDur(a.durationMs) + '</span>' +
|
||||
'</div>';
|
||||
alist.appendChild(row);
|
||||
});
|
||||
asec.appendChild(alist);
|
||||
body.appendChild(asec);
|
||||
}
|
||||
|
||||
if (wf.startedAt) {
|
||||
var ts = document.createElement('div');
|
||||
ts.className = 'wf-meta';
|
||||
ts.textContent = 'Start: ' + _wfTime(wf.startedAt) +
|
||||
(wf.lastActivityAt ? ' · letzte Aktivität: ' + _wfTime(wf.lastActivityAt) : '');
|
||||
ts.textContent = 'Start: ' + _wfTime(wf.startedAt);
|
||||
body.appendChild(ts);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user