From 6ab6bbbbd456b6424d4f39ad5e11376050d4349f Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Mon, 15 Jun 2026 10:49:22 +0200 Subject: [PATCH] =?UTF-8?q?feat(ultracode):=20Phase=204=20=E2=80=94=20clic?= =?UTF-8?q?k=20an=20agent=20card=20to=20open=20its=20live=20transcript?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each workflow agent card with an agentId is now clickable and opens that agent's live transcript in a popup, reusing the existing GET /api/subagents/:agentId/ transcript route. The workflow agent's agentId is byte-identical to the agent-.jsonl stem that subagent-watcher already tracks (via w16's watchWorkflowDirs), so this is a pure client-side join โ€” ZERO subagent-watcher edits. Graceful degradation: 'start' (queued) agents have no agentId yet and stay non-clickable; an aged-out/untracked agent (subagent-watcher's 4h startup window, or tracking disabled) returns an empty transcript and shows a friendly note instead of an empty popup. Verified on a live isolated server: the subagent transcript route serves a workflow agent's transcript (150 entries) and the runId's agents[] carries the matching agentId; Playwright confirmed clicking a card opens the transcript popup with no console errors. frontend-syntax / public-assets / CSS-parse clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/web/public/styles.css | 7 +++++ src/web/public/ultracode-panel.js | 43 ++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/web/public/styles.css b/src/web/public/styles.css index 7076386a..ad66aeb5 100644 --- a/src/web/public/styles.css +++ b/src/web/public/styles.css @@ -8486,6 +8486,13 @@ kbd { padding: 0.4rem 0.5rem; margin-bottom: 0.35rem; } +.ultracode-agent-card--clickable { + cursor: pointer; + transition: background 0.15s; +} +.ultracode-agent-card--clickable:hover { + background: var(--bg-card); +} .ultracode-agent-top { display: flex; align-items: center; diff --git a/src/web/public/ultracode-panel.js b/src/web/public/ultracode-panel.js index 76f89e54..fb97078e 100644 --- a/src/web/public/ultracode-panel.js +++ b/src/web/public/ultracode-panel.js @@ -103,6 +103,39 @@ Object.assign(CodemanApp.prototype, { } }, + // Phase 4: open an agent's live transcript by agentId. The workflow agent's + // agentId is byte-identical to the agent-.jsonl stem already tracked by + // subagent-watcher, so we reuse the existing transcript route โ€” no watcher edits. + // Graceful when the agent isn't tracked yet / aged out / tracking disabled. + async openWorkflowAgentTranscript(agentId) { + if (!agentId) return; + let data = null; + try { + const res = await fetch(`/api/subagents/${encodeURIComponent(agentId)}/transcript?format=formatted`); + data = await res.json(); + } catch { + data = null; + } + const ok = data && data.success && data.data; + const formatted = ok ? data.data.formatted : null; + const entryCount = ok ? data.data.entryCount || 0 : 0; + if (!formatted || !entryCount) { + alert( + 'No transcript available for this agent yet โ€” it may be queued, aged out of tracking, or subagent tracking is disabled.' + ); + return; + } + const win = window.open('', '_blank', 'width=860,height=640'); + if (!win) return; // popup blocked + win.document.write( + `Workflow agent ${escapeHtml(agentId)} transcript` + + `` + + `

Workflow agent ${escapeHtml(agentId)} (${entryCount} entries)

` + + `
${escapeHtml(formatted.join('\n'))}
` + ); + win.document.close(); + }, + // ----- Render (debounced) ----- renderUltracodeAgentsPanel() { clearTimeout(this._ultracodeRenderTimer); @@ -220,8 +253,16 @@ Object.assign(CodemanApp.prototype, { } else if (a.lastToolName) { secondary = escapeHtml(a.lastToolName + (a.lastToolSummary ? ' ยท ' + a.lastToolSummary : '')); } + // Phase 4: cards with an agentId open the live transcript (the agentId is byte-identical + // to the agent-.jsonl stem already tracked by subagent-watcher). 'start' agents have + // no agentId yet, so they stay non-clickable. + const clickable = !!a.agentId; + const cardAttrs = clickable + ? ` class="ultracode-agent-card ultracode-agent-card--clickable" role="button" tabindex="0"` + + ` title="View transcript" onclick="app.openWorkflowAgentTranscript('${escapeHtml(a.agentId)}')"` + : ` class="ultracode-agent-card"`; return ( - `
` + + `` + `
` + `${escapeHtml(a.label || 'agent')}` + `${escapeHtml(stateLabel)}` +