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)}` +