aicodeman 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/config/workflow-config.d.ts +24 -0
  2. package/dist/config/workflow-config.d.ts.map +1 -0
  3. package/dist/config/workflow-config.js +24 -0
  4. package/dist/config/workflow-config.js.map +1 -0
  5. package/dist/subagent-watcher.d.ts +34 -0
  6. package/dist/subagent-watcher.d.ts.map +1 -1
  7. package/dist/subagent-watcher.js +147 -4
  8. package/dist/subagent-watcher.js.map +1 -1
  9. package/dist/types/index.d.ts +1 -0
  10. package/dist/types/index.d.ts.map +1 -1
  11. package/dist/types/index.js +1 -0
  12. package/dist/types/index.js.map +1 -1
  13. package/dist/types/workflow-run.d.ts +130 -0
  14. package/dist/types/workflow-run.d.ts.map +1 -0
  15. package/dist/types/workflow-run.js +20 -0
  16. package/dist/types/workflow-run.js.map +1 -0
  17. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  18. package/dist/web/public/{app.92f49a9d.js → app.6b133aaf.js} +6 -6
  19. package/dist/web/public/app.6b133aaf.js.br +0 -0
  20. package/dist/web/public/app.6b133aaf.js.gz +0 -0
  21. package/dist/web/public/{constants.59faac65.js → constants.1c779517.js} +5 -0
  22. package/dist/web/public/constants.1c779517.js.br +0 -0
  23. package/dist/web/public/constants.1c779517.js.gz +0 -0
  24. package/dist/web/public/image-input.0ea86695.js.gz +0 -0
  25. package/dist/web/public/index.html +48 -8
  26. package/dist/web/public/index.html.br +0 -0
  27. package/dist/web/public/index.html.gz +0 -0
  28. package/dist/web/public/input-cjk.b8686b5e.js.gz +0 -0
  29. package/dist/web/public/keyboard-accessory.bc753cc7.js.gz +0 -0
  30. package/dist/web/public/mobile-handlers.db3dc3c8.js.gz +0 -0
  31. package/dist/web/public/mobile.06b38d3a.css.gz +0 -0
  32. package/dist/web/public/notification-manager.9c984ac2.js.gz +0 -0
  33. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  34. package/dist/web/public/{panels-ui.2f467969.js → panels-ui.f3f08e26.js} +48 -48
  35. package/dist/web/public/panels-ui.f3f08e26.js.br +0 -0
  36. package/dist/web/public/panels-ui.f3f08e26.js.gz +0 -0
  37. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  38. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  39. package/dist/web/public/respawn-ui.2d249da9.js.gz +0 -0
  40. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  41. package/dist/web/public/session-ui.1463b824.js.gz +0 -0
  42. package/dist/web/public/settings-ui.08f7708b.js +55 -0
  43. package/dist/web/public/settings-ui.08f7708b.js.br +0 -0
  44. package/dist/web/public/settings-ui.08f7708b.js.gz +0 -0
  45. package/dist/web/public/{styles.8e1ea0c6.css → styles.379f31e0.css} +1 -1
  46. package/dist/web/public/styles.379f31e0.css.br +0 -0
  47. package/dist/web/public/styles.379f31e0.css.gz +0 -0
  48. package/dist/web/public/{subagent-windows.a366a4ad.js → subagent-windows.07e139f2.js} +9 -0
  49. package/dist/web/public/subagent-windows.07e139f2.js.br +0 -0
  50. package/dist/web/public/subagent-windows.07e139f2.js.gz +0 -0
  51. package/dist/web/public/sw.js.gz +0 -0
  52. package/dist/web/public/terminal-ui.a7e046da.js.gz +0 -0
  53. package/dist/web/public/ultracode-panel.js +314 -0
  54. package/dist/web/public/ultracode-panel.js.br +0 -0
  55. package/dist/web/public/ultracode-panel.js.gz +0 -0
  56. package/dist/web/public/ultracode-windows.js +382 -0
  57. package/dist/web/public/ultracode-windows.js.br +0 -0
  58. package/dist/web/public/ultracode-windows.js.gz +0 -0
  59. package/dist/web/public/upload.html.gz +0 -0
  60. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  61. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  62. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  63. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  64. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  65. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  66. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  67. package/dist/web/public/vendor/xterm.css.gz +0 -0
  68. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  69. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  70. package/dist/web/routes/file-routes.d.ts.map +1 -1
  71. package/dist/web/routes/file-routes.js +80 -24
  72. package/dist/web/routes/file-routes.js.map +1 -1
  73. package/dist/web/routes/system-routes.d.ts.map +1 -1
  74. package/dist/web/routes/system-routes.js +23 -0
  75. package/dist/web/routes/system-routes.js.map +1 -1
  76. package/dist/web/schemas.d.ts +2 -0
  77. package/dist/web/schemas.d.ts.map +1 -1
  78. package/dist/web/schemas.js +4 -0
  79. package/dist/web/schemas.js.map +1 -1
  80. package/dist/web/server.d.ts +14 -0
  81. package/dist/web/server.d.ts.map +1 -1
  82. package/dist/web/server.js +57 -0
  83. package/dist/web/server.js.map +1 -1
  84. package/dist/web/sse-events.d.ts +10 -0
  85. package/dist/web/sse-events.d.ts.map +1 -1
  86. package/dist/web/sse-events.js +12 -0
  87. package/dist/web/sse-events.js.map +1 -1
  88. package/dist/workflow-run-watcher.d.ts +76 -0
  89. package/dist/workflow-run-watcher.d.ts.map +1 -0
  90. package/dist/workflow-run-watcher.js +327 -0
  91. package/dist/workflow-run-watcher.js.map +1 -0
  92. package/package.json +1 -1
  93. package/dist/web/public/app.92f49a9d.js.br +0 -0
  94. package/dist/web/public/app.92f49a9d.js.gz +0 -0
  95. package/dist/web/public/constants.59faac65.js.br +0 -0
  96. package/dist/web/public/constants.59faac65.js.gz +0 -0
  97. package/dist/web/public/panels-ui.2f467969.js.br +0 -0
  98. package/dist/web/public/panels-ui.2f467969.js.gz +0 -0
  99. package/dist/web/public/settings-ui.44b99ce0.js +0 -55
  100. package/dist/web/public/settings-ui.44b99ce0.js.br +0 -0
  101. package/dist/web/public/settings-ui.44b99ce0.js.gz +0 -0
  102. package/dist/web/public/styles.8e1ea0c6.css.br +0 -0
  103. package/dist/web/public/styles.8e1ea0c6.css.gz +0 -0
  104. package/dist/web/public/subagent-windows.a366a4ad.js.br +0 -0
  105. package/dist/web/public/subagent-windows.a366a4ad.js.gz +0 -0
@@ -455,6 +455,12 @@ Object.assign(CodemanApp.prototype, {
455
455
  svg.appendChild(line);
456
456
  }
457
457
  }
458
+
459
+ // Ultracode floating run windows → parent tab (additional layer, ultracode-windows.js).
460
+ // Drawn into the same SVG and same batched read/write pass; the tab-rect cache is shared.
461
+ if (typeof this._appendUltracodeConnectionLines === 'function') {
462
+ this._appendUltracodeConnectionLines(svg, rects);
463
+ }
458
464
  },
459
465
 
460
466
  // ═══════════════════════════════════════════════════════════════
@@ -975,6 +981,9 @@ Object.assign(CodemanApp.prototype, {
975
981
  }
976
982
  this.imagePopups.clear();
977
983
 
984
+ // Clean up ultracode floating run windows (re-seeded from data.workflowRuns on reconnect)
985
+ if (typeof this.removeAllUltracodeWindows === 'function') this.removeAllUltracodeWindows();
986
+
978
987
  // Clear orphaned plan generation state
979
988
  this.activePlanOrchestratorId = null;
980
989
  this._planProgressHandler = null;
Binary file
@@ -0,0 +1,314 @@
1
+ /**
2
+ * @fileoverview Ultracode / Workflow run visualization — master-detail dock panel.
3
+ *
4
+ * Mirrors Claude Code's "working agents" TUI: LEFT pane = runs and their phases
5
+ * (selectable "tasks"), RIGHT pane = the selected run's agents with model, live
6
+ * state, TOKENS burned, and TOOL CALLS. Opt-in via the `showUltracodeAgents`
7
+ * setting; the launcher button + panel are hidden until enabled.
8
+ *
9
+ * Data: run SUMMARIES arrive via getLightState (`data.workflowRuns`) and the
10
+ * `workflow:run_*` SSE events (LEFT list). The full run (with agents[]) is fetched
11
+ * per-run from GET /api/workflows/:runId when a run is selected (RIGHT pane).
12
+ *
13
+ * Standalone: reads only the workflow-run endpoints; never touches subagent state.
14
+ *
15
+ * @mixin Extends CodemanApp.prototype via Object.assign
16
+ * @loadorder 11.5 (after panels-ui.js, before session-ui.js)
17
+ */
18
+ /* global CodemanApp, SSE_EVENTS, escapeHtml */
19
+
20
+ Object.assign(CodemanApp.prototype, {
21
+ /** Ensure workflow state maps exist (lazy — constructor also seeds them). */
22
+ _ensureWorkflowState() {
23
+ if (!this.workflowRuns) this.workflowRuns = new Map(); // runId -> summary
24
+ if (!this.workflowRunDetails) this.workflowRunDetails = new Map(); // runId -> full run (with agents)
25
+ if (this.activeWorkflowRunId === undefined) this.activeWorkflowRunId = null;
26
+ if (this.activeWorkflowPhaseIndex === undefined) this.activeWorkflowPhaseIndex = null;
27
+ },
28
+
29
+ /** Seed the LEFT list from a getLightState snapshot (array of run summaries). */
30
+ seedWorkflowRuns(summaries) {
31
+ this._ensureWorkflowState();
32
+ this.workflowRuns.clear();
33
+ (summaries || []).forEach((s) => this.workflowRuns.set(s.runId, s));
34
+ // Restore floating windows for runs that are still active & recent (additional layer).
35
+ if (typeof this._syncUltracodeFloatingWindow === 'function') {
36
+ (summaries || []).forEach((s) => this._syncUltracodeFloatingWindow(s, { fromSeed: true }));
37
+ }
38
+ this.renderUltracodeAgentsPanel();
39
+ },
40
+
41
+ // ----- SSE handlers (wired in app.js _SSE_HANDLER_MAP) -----
42
+ _onWorkflowRunDiscovered(data) {
43
+ this._upsertWorkflowRun(data);
44
+ },
45
+ _onWorkflowRunUpdated(data) {
46
+ this._upsertWorkflowRun(data);
47
+ },
48
+ _onWorkflowRunRemoved(data) {
49
+ this._ensureWorkflowState();
50
+ if (!data || !data.runId) return;
51
+ this.workflowRuns.delete(data.runId);
52
+ this.workflowRunDetails.delete(data.runId);
53
+ if (this.activeWorkflowRunId === data.runId) this.activeWorkflowRunId = null;
54
+ // Retire the floating run window too (additional layer — ultracode-windows.js).
55
+ if (typeof this.closeUltracodeWindow === 'function') this.closeUltracodeWindow(data.runId, false);
56
+ this.renderUltracodeAgentsPanel();
57
+ },
58
+
59
+ _upsertWorkflowRun(summary) {
60
+ this._ensureWorkflowState();
61
+ if (!summary || !summary.runId) return;
62
+ this.workflowRuns.set(summary.runId, summary);
63
+ // If the live-updating run is the one open in the detail pane, refresh its agents.
64
+ if (this.activeWorkflowRunId === summary.runId) {
65
+ this._fetchWorkflowRunDetail(summary.runId);
66
+ }
67
+ // Auto-pop / refresh the floating run window for active runs (additional layer).
68
+ if (typeof this._syncUltracodeFloatingWindow === 'function') this._syncUltracodeFloatingWindow(summary);
69
+ this.renderUltracodeAgentsPanel();
70
+ },
71
+
72
+ // ----- Panel open/close -----
73
+ toggleUltracodeAgentsPanel() {
74
+ const panel = document.getElementById('ultracodeAgentsPanel');
75
+ if (!panel) return;
76
+ panel.classList.remove('hidden');
77
+ panel.classList.toggle('open');
78
+ if (panel.classList.contains('open')) this.renderUltracodeAgentsPanel();
79
+ },
80
+ closeUltracodeAgentsPanel() {
81
+ const panel = document.getElementById('ultracodeAgentsPanel');
82
+ if (panel) panel.classList.remove('open');
83
+ },
84
+
85
+ // ----- Selection -----
86
+ selectWorkflowRun(runId) {
87
+ this._ensureWorkflowState();
88
+ this.activeWorkflowRunId = runId;
89
+ this.activeWorkflowPhaseIndex = null; // reset phase filter on run change
90
+ this._fetchWorkflowRunDetail(runId);
91
+ this.renderUltracodeAgentsPanel();
92
+ },
93
+ selectWorkflowPhase(phaseIndex) {
94
+ this._ensureWorkflowState();
95
+ // phaseIndex null => show all phases
96
+ this.activeWorkflowPhaseIndex = phaseIndex === null || phaseIndex === undefined ? null : Number(phaseIndex);
97
+ this._renderUltracodeDetail();
98
+ },
99
+
100
+ async _fetchWorkflowRunDetail(runId) {
101
+ try {
102
+ const res = await fetch(`/api/workflows/${encodeURIComponent(runId)}`);
103
+ const env = await res.json();
104
+ const run = env && env.success ? env.data : null;
105
+ if (run) {
106
+ this.workflowRunDetails.set(runId, run);
107
+ if (this.activeWorkflowRunId === runId) this._renderUltracodeDetail();
108
+ // Refresh the floating window (if one is open for this run) with the fetched agents[].
109
+ if (this.ultracodeWindows && this.ultracodeWindows.has(runId)) this.renderUltracodeWindowContent(runId);
110
+ }
111
+ } catch {
112
+ /* transient — next update retries */
113
+ }
114
+ },
115
+
116
+ // Phase 4: open an agent's live transcript by agentId. The workflow agent's
117
+ // agentId is byte-identical to the agent-<id>.jsonl stem already tracked by
118
+ // subagent-watcher, so we reuse the existing transcript route — no watcher edits.
119
+ // Graceful when the agent isn't tracked yet / aged out / tracking disabled.
120
+ async openWorkflowAgentTranscript(agentId) {
121
+ if (!agentId) return;
122
+ let data = null;
123
+ try {
124
+ const res = await fetch(`/api/subagents/${encodeURIComponent(agentId)}/transcript?format=formatted`);
125
+ data = await res.json();
126
+ } catch {
127
+ data = null;
128
+ }
129
+ const ok = data && data.success && data.data;
130
+ const formatted = ok ? data.data.formatted : null;
131
+ const entryCount = ok ? data.data.entryCount || 0 : 0;
132
+ if (!formatted || !entryCount) {
133
+ alert(
134
+ 'No transcript available for this agent yet — it may be queued, aged out of tracking, or subagent tracking is disabled.'
135
+ );
136
+ return;
137
+ }
138
+ const win = window.open('', '_blank', 'width=860,height=640');
139
+ if (!win) return; // popup blocked
140
+ win.document.write(
141
+ `<html><head><title>Workflow agent ${escapeHtml(agentId)} transcript</title>` +
142
+ `<style>body{background:#1a1a2e;color:#eee;font-family:monospace;padding:20px}pre{white-space:pre-wrap;word-wrap:break-word}</style>` +
143
+ `</head><body><h2>Workflow agent ${escapeHtml(agentId)} (${entryCount} entries)</h2>` +
144
+ `<pre>${escapeHtml(formatted.join('\n'))}</pre></body></html>`
145
+ );
146
+ win.document.close();
147
+ },
148
+
149
+ // ----- Render (debounced) -----
150
+ renderUltracodeAgentsPanel() {
151
+ clearTimeout(this._ultracodeRenderTimer);
152
+ this._ultracodeRenderTimer = setTimeout(() => this._renderUltracodeAgentsPanelImmediate(), 150);
153
+ },
154
+
155
+ _renderUltracodeAgentsPanelImmediate() {
156
+ this._ensureWorkflowState();
157
+ const panel = document.getElementById('ultracodeAgentsPanel');
158
+ if (!panel) return;
159
+ const badge = document.getElementById('ultracodeCountBadge');
160
+ if (badge) badge.textContent = this.workflowRuns.size ? String(this.workflowRuns.size) : '';
161
+ this._renderUltracodeRunList();
162
+ this._renderUltracodeDetail();
163
+ },
164
+
165
+ _renderUltracodeRunList() {
166
+ const list = document.getElementById('ultracodeRunList');
167
+ if (!list) return;
168
+ const runs = Array.from(this.workflowRuns.values()).sort(
169
+ (a, b) => (b.lastActivityAt || 0) - (a.lastActivityAt || 0)
170
+ );
171
+ if (!runs.length) {
172
+ list.innerHTML = '<div class="subagent-empty">No ultracode runs detected</div>';
173
+ return;
174
+ }
175
+ list.innerHTML = runs.map((r) => this._workflowRunRowHtml(r)).join('');
176
+ },
177
+
178
+ _workflowRunRowHtml(r) {
179
+ const active = r.runId === this.activeWorkflowRunId;
180
+ const name = escapeHtml(r.workflowName || r.summary || r.runId);
181
+ const status = String(r.status || '');
182
+ const statusCls = this._workflowStatusClass(status);
183
+ const stats = `${r.agentCount ?? 0} agents · ${this._fmtNum(r.totalTokens)} tok · ${r.totalToolCalls ?? 0} tools`;
184
+ let phasesHtml = '';
185
+ if (active && Array.isArray(r.phases) && r.phases.length) {
186
+ const allActive = this.activeWorkflowPhaseIndex === null ? ' selected' : '';
187
+ const chips = [
188
+ `<div class="ultracode-phase-chip${allActive}" onclick="event.stopPropagation();app.selectWorkflowPhase(null)">All</div>`,
189
+ ];
190
+ r.phases.forEach((p, i) => {
191
+ const sel = this.activeWorkflowPhaseIndex === i + 1 ? ' selected' : '';
192
+ chips.push(
193
+ `<div class="ultracode-phase-chip${sel}" title="${escapeHtml(p.detail || '')}" onclick="event.stopPropagation();app.selectWorkflowPhase(${i + 1})">${escapeHtml(p.title || 'Phase ' + (i + 1))}</div>`
194
+ );
195
+ });
196
+ phasesHtml = `<div class="ultracode-phase-list">${chips.join('')}</div>`;
197
+ }
198
+ return (
199
+ `<div class="ultracode-run-item${active ? ' selected' : ''}" onclick="app.selectWorkflowRun('${escapeHtml(r.runId)}')">` +
200
+ `<div class="ultracode-run-head"><span class="ultracode-run-name">${name}</span>` +
201
+ `<span class="ultracode-status ${statusCls}">${escapeHtml(status || '—')}</span></div>` +
202
+ `<div class="ultracode-run-stats">${escapeHtml(stats)}</div>` +
203
+ phasesHtml +
204
+ `</div>`
205
+ );
206
+ },
207
+
208
+ _renderUltracodeDetail() {
209
+ const detail = document.getElementById('ultracodeAgentGrid');
210
+ if (!detail) return;
211
+ const runId = this.activeWorkflowRunId;
212
+ if (!runId) {
213
+ detail.innerHTML = '<div class="subagent-empty">Select a run to view its agents</div>';
214
+ return;
215
+ }
216
+ const run = this.workflowRunDetails.get(runId);
217
+ if (!run) {
218
+ detail.innerHTML = '<div class="subagent-empty">Loading agents…</div>';
219
+ return;
220
+ }
221
+ const phases = Array.isArray(run.phases) ? run.phases : [];
222
+ let agents = Array.isArray(run.agents) ? run.agents : [];
223
+ if (this.activeWorkflowPhaseIndex !== null) {
224
+ agents = agents.filter((a) => a.phaseIndex === this.activeWorkflowPhaseIndex);
225
+ }
226
+ if (!agents.length) {
227
+ detail.innerHTML = '<div class="subagent-empty">No agents in this view</div>';
228
+ return;
229
+ }
230
+ // Group agents by phaseIndex, in phase order.
231
+ const groups = new Map();
232
+ agents.forEach((a) => {
233
+ const key = a.phaseIndex || 0;
234
+ if (!groups.has(key)) groups.set(key, []);
235
+ groups.get(key).push(a);
236
+ });
237
+ const orderedKeys = Array.from(groups.keys()).sort((a, b) => a - b);
238
+ const html = orderedKeys
239
+ .map((key) => {
240
+ const group = groups.get(key);
241
+ const title = (phases[key - 1] && phases[key - 1].title) || `Phase ${key}`;
242
+ const tok = group.reduce((s, a) => s + (a.tokens || 0), 0);
243
+ const tools = group.reduce((s, a) => s + (a.toolCalls || 0), 0);
244
+ const header =
245
+ `<div class="ultracode-phase-header"><span>${escapeHtml(title)}</span>` +
246
+ `<span class="ultracode-phase-sub">${this._fmtNum(tok)} tok · ${tools} tools</span></div>`;
247
+ return header + group.map((a) => this._workflowAgentCardHtml(a)).join('');
248
+ })
249
+ .join('');
250
+ detail.innerHTML = html;
251
+ },
252
+
253
+ _workflowAgentCardHtml(a) {
254
+ const state = String(a.state || 'start');
255
+ const stateCls = this._workflowAgentStateClass(state);
256
+ const stateLabel = state === 'start' ? 'queued' : state === 'progress' ? 'running' : state;
257
+ const model = this._modelShort(a.model);
258
+ const tokens = a.tokens === undefined ? '—' : this._fmtNum(a.tokens);
259
+ const tools = a.toolCalls === undefined ? '—' : String(a.toolCalls);
260
+ let secondary = '';
261
+ if (state === 'done' && a.resultPreview) {
262
+ secondary = escapeHtml(a.resultPreview);
263
+ } else if (a.lastToolName) {
264
+ secondary = escapeHtml(a.lastToolName + (a.lastToolSummary ? ' · ' + a.lastToolSummary : ''));
265
+ }
266
+ // Phase 4: cards with an agentId open the live transcript (the agentId is byte-identical
267
+ // to the agent-<id>.jsonl stem already tracked by subagent-watcher). 'start' agents have
268
+ // no agentId yet, so they stay non-clickable.
269
+ const clickable = !!a.agentId;
270
+ const cardAttrs = clickable
271
+ ? ` class="ultracode-agent-card ultracode-agent-card--clickable" role="button" tabindex="0"` +
272
+ ` title="View transcript" onclick="app.openWorkflowAgentTranscript('${escapeHtml(a.agentId)}')"`
273
+ : ` class="ultracode-agent-card"`;
274
+ return (
275
+ `<div${cardAttrs}>` +
276
+ `<div class="ultracode-agent-top">` +
277
+ `<span class="ultracode-agent-label">${escapeHtml(a.label || 'agent')}</span>` +
278
+ `<span class="ultracode-agent-state ${stateCls}">${escapeHtml(stateLabel)}</span>` +
279
+ `</div>` +
280
+ `<div class="ultracode-agent-meta">` +
281
+ `<span class="ultracode-chip" title="model">${escapeHtml(model)}</span>` +
282
+ `<span class="ultracode-chip ultracode-chip-tok" title="tokens burned">${tokens} tok</span>` +
283
+ `<span class="ultracode-chip ultracode-chip-tool" title="tool calls">${tools} tools</span>` +
284
+ `</div>` +
285
+ (secondary ? `<div class="ultracode-agent-sub">${secondary}</div>` : '') +
286
+ `</div>`
287
+ );
288
+ },
289
+
290
+ // ----- helpers -----
291
+ _workflowStatusClass(status) {
292
+ if (status === 'completed') return 'completed';
293
+ if (status === 'running') return 'active';
294
+ if (status === 'killed' || status === 'failed') return 'failed';
295
+ return '';
296
+ },
297
+ _workflowAgentStateClass(state) {
298
+ if (state === 'done') return 'completed';
299
+ if (state === 'progress') return 'active';
300
+ return 'idle'; // start / queued
301
+ },
302
+ _modelShort(model) {
303
+ if (!model) return '';
304
+ return String(model)
305
+ .replace(/^claude-/, '')
306
+ .replace(/-\d{8}$/, '');
307
+ },
308
+ _fmtNum(n) {
309
+ if (n === undefined || n === null) return '0';
310
+ if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';
311
+ if (n >= 1000) return (n / 1000).toFixed(1) + 'k';
312
+ return String(n);
313
+ },
314
+ });