feat(ultracode): master-detail tab for Workflow/ultracode run visualization

Opt-in (showUltracodeAgents, default OFF) panel that visualizes ultracode /
Workflow-tool runs like Claude Code's "working agents" TUI: LEFT = runs + phases
(selectable tasks), RIGHT = each run's agents with model, live state, tokens
burned, and tool calls.

Standalone — ZERO edits to subagent-watcher.ts. A new workflow-run-watcher.ts
singleton globs the run-state tree (~/.claude/projects/*/*/workflows/wf_*.json,
disjoint from the transcript tree), strips the heavy script/scriptPath/result/logs
fields (174KB -> ~25KB/run), and emits workflow:run_* SSE events. The LEFT list
ships lightweight summaries (getLightState replay + SSE); the RIGHT pane fetches
the full run (with agents[]) via GET /api/workflows/:runId on selection.

Backend: workflow-run-watcher.ts, types/workflow-run.ts, config/workflow-config.ts,
3 SSE events, getLightState workflowRuns replay, GET /api/workflows[/:runId],
showUltracodeAgents schema key + boot-gate (default OFF) + live toggleService.
Frontend: ultracode-panel.js (debounced master-detail render, run/phase select),
header launcher (btn-ultracode-agents--hidden marker -> mobile-guard-exempt),
App Settings toggle (SYNCED, deliberately not in displayKeys).

Agent states on disk are start|progress|done (start=queued; done has
durationMs/resultPreview). Tests: workflow-run-watcher (9), workflow-routes (3).
Verified: tsc/lint/prettier/frontend-syntax/public-assets/mobile-header-guard
clean; full test:ci green (2986 passed); live server + Playwright e2e against 25
real runs (28-agent grid, phase filter, OFF hides launcher).

Design: docs/ultracode-agent-viz-plan.md (rev. 3).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-06-15 08:40:05 +02:00
parent f6a30d7335
commit c15c19fab7
17 changed files with 1800 additions and 1 deletions
+194
View File
@@ -8355,6 +8355,200 @@ kbd {
display: none !important;
}
/* "Ultracode Agents" header launcher — opt-in (App Settings → Display), hidden by
default everywhere (so the mobile-header-buttons-policy guard auto-excludes it).
Base inline-flex !important + a more-specific marker rule to hide. */
.btn-ultracode-agents {
display: inline-flex !important;
}
.btn-ultracode-agents.btn-ultracode-agents--hidden {
display: none !important;
}
/* ===== Ultracode / Workflow agents master-detail panel ===== */
/* Reuses .subagents-panel dock behavior but anchors LEFT so it never overlaps the
bottom-right subagents panel when both are open. */
.ultracode-agents-panel {
right: auto;
left: 0.5rem;
width: 640px;
}
@media (max-width: 700px) {
.ultracode-agents-panel {
left: 0;
right: 0;
width: 100%;
max-width: 100%;
}
}
/* LEFT pane: run rows */
.ultracode-run-item {
padding: 0.45rem 0.5rem;
border-radius: 6px;
background: var(--bg-input);
margin-bottom: 0.4rem;
cursor: pointer;
transition: background 0.15s;
}
.ultracode-run-item:hover {
background: var(--bg-card);
}
.ultracode-run-item.selected {
background: var(--bg-card);
border: 1px solid var(--accent);
}
.ultracode-run-head {
display: flex;
align-items: center;
gap: 0.4rem;
margin-bottom: 0.2rem;
}
.ultracode-run-name {
font-size: 0.75rem;
font-weight: 600;
color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ultracode-run-stats {
font-size: 0.65rem;
color: var(--text-dim);
}
.ultracode-status {
font-size: 0.58rem;
padding: 0.05rem 0.3rem;
border-radius: 4px;
margin-left: auto;
text-transform: lowercase;
background: var(--text-muted);
color: white;
flex-shrink: 0;
}
.ultracode-status.completed {
background: var(--success);
}
.ultracode-status.active {
background: var(--warning);
color: black;
}
.ultracode-status.failed {
background: var(--error, #b3261e);
color: white;
}
/* LEFT pane: phase chips for the selected run */
.ultracode-phase-list {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
margin-top: 0.4rem;
}
.ultracode-phase-chip {
font-size: 0.62rem;
padding: 0.1rem 0.4rem;
border-radius: 10px;
background: var(--bg-card);
border: 1px solid var(--border);
color: var(--text-muted);
cursor: pointer;
}
.ultracode-phase-chip:hover {
color: var(--text);
}
.ultracode-phase-chip.selected {
background: var(--accent);
border-color: var(--accent);
color: white;
}
/* RIGHT pane: per-phase header + agent cards */
.ultracode-phase-header {
display: flex;
align-items: baseline;
justify-content: space-between;
font-size: 0.7rem;
font-weight: 600;
color: var(--text);
margin: 0.5rem 0 0.3rem;
padding-bottom: 0.2rem;
border-bottom: 1px solid var(--border);
}
.ultracode-phase-sub {
font-size: 0.62rem;
font-weight: 400;
color: var(--text-dim);
}
.ultracode-agent-card {
background: var(--bg-input);
border-radius: 6px;
padding: 0.4rem 0.5rem;
margin-bottom: 0.35rem;
}
.ultracode-agent-top {
display: flex;
align-items: center;
gap: 0.4rem;
margin-bottom: 0.25rem;
}
.ultracode-agent-label {
font-size: 0.72rem;
font-weight: 600;
color: var(--text);
font-family: var(--font-mono);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ultracode-agent-state {
font-size: 0.56rem;
padding: 0.05rem 0.3rem;
border-radius: 4px;
margin-left: auto;
flex-shrink: 0;
background: var(--text-muted);
color: white;
}
.ultracode-agent-state.completed {
background: var(--success);
}
.ultracode-agent-state.active {
background: var(--warning);
color: black;
}
.ultracode-agent-state.idle {
background: var(--bg-card);
color: var(--text-muted);
}
.ultracode-agent-meta {
display: flex;
flex-wrap: wrap;
gap: 0.3rem;
}
.ultracode-chip {
font-size: 0.62rem;
padding: 0.05rem 0.35rem;
border-radius: 4px;
background: var(--bg-card);
color: var(--text-muted);
font-family: var(--font-mono);
}
.ultracode-chip-tok {
color: var(--accent);
}
.ultracode-chip-tool {
color: var(--text);
}
.ultracode-agent-sub {
font-size: 0.62rem;
color: var(--text-dim);
margin-top: 0.25rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Plan-usage chip (App Settings → Display → "Plan Usage Limits"). Shows the
live 5-hour + weekly plan limits parsed from the Claude statusline. Hidden by
default via the marker class below; the server strips it at render when the