@shapeshift-labs/frontier-loom-ui 0.1.0
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.
- package/README.md +28 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +105 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.js +3827 -0
- package/dist/client.js.map +1 -0
- package/dist/frontier.d.ts +1 -0
- package/dist/frontier.js +37 -0
- package/dist/frontier.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/public/index.html +16 -0
- package/dist/public/styles.css +3278 -0
- package/dist/server.d.ts +113 -0
- package/dist/server.js +1731 -0
- package/dist/server.js.map +1 -0
- package/features/loom-ui-dashboard.json +28 -0
- package/frontier.config.mjs +39 -0
- package/package.json +67 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,3827 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@shapeshift-labs/frontier-dom/jsx-runtime";
|
|
2
|
+
const contentTabs = [
|
|
3
|
+
{ id: 'work', label: 'Overview' },
|
|
4
|
+
{ id: 'board', label: 'Board' },
|
|
5
|
+
{ id: 'swarm', label: 'Swarm' },
|
|
6
|
+
{ id: 'performance', label: 'Performance' },
|
|
7
|
+
{ id: 'history', label: 'History' },
|
|
8
|
+
{ id: 'testing', label: 'Testing' },
|
|
9
|
+
{ id: 'actions', label: 'Questions' }
|
|
10
|
+
];
|
|
11
|
+
const root = document.getElementById('app');
|
|
12
|
+
const scrollPositions = new Map();
|
|
13
|
+
const initialRoute = routeStateFromLocation();
|
|
14
|
+
let selectedTab = initialRoute.tab;
|
|
15
|
+
let currentDashboard;
|
|
16
|
+
let renderedDashboardSignature;
|
|
17
|
+
let pendingFocusTab;
|
|
18
|
+
let selectedTaskCardId = initialRoute.taskId;
|
|
19
|
+
let selectedTicketId = initialRoute.ticket;
|
|
20
|
+
let chartPopover;
|
|
21
|
+
let activeContributionTarget;
|
|
22
|
+
const taskDetailsCache = new Map();
|
|
23
|
+
const taskDetailsPending = new Set();
|
|
24
|
+
const humanAnswerDrafts = new Map();
|
|
25
|
+
const humanAnswerStates = new Map();
|
|
26
|
+
root?.addEventListener('click', (event) => {
|
|
27
|
+
const copyTarget = event.target instanceof Element
|
|
28
|
+
? event.target.closest('[data-copy-code]')
|
|
29
|
+
: null;
|
|
30
|
+
if (copyTarget) {
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
event.stopPropagation();
|
|
33
|
+
const code = copyTarget.dataset.copyCode ?? '';
|
|
34
|
+
if (code)
|
|
35
|
+
void copyTextToClipboard(code, copyTarget);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const revealTarget = event.target instanceof Element
|
|
39
|
+
? event.target.closest('[data-reveal-artifact-path]')
|
|
40
|
+
: null;
|
|
41
|
+
if (revealTarget) {
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
event.stopPropagation();
|
|
44
|
+
const artifactPath = revealTarget.dataset.revealArtifactPath ?? '';
|
|
45
|
+
if (artifactPath)
|
|
46
|
+
void revealArtifactInFinder(artifactPath);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const modalClose = event.target instanceof Element
|
|
50
|
+
? event.target.closest('[data-modal-close]')
|
|
51
|
+
: null;
|
|
52
|
+
const modalBackdrop = event.target instanceof HTMLElement && event.target.dataset.modalBackdrop === 'true';
|
|
53
|
+
if (modalClose || modalBackdrop) {
|
|
54
|
+
closeTaskDialog();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const tab = event.target instanceof Element
|
|
58
|
+
? event.target.closest('[data-content-tab]')
|
|
59
|
+
: null;
|
|
60
|
+
if (tab) {
|
|
61
|
+
selectContentTab(asContentTab(tab.dataset.contentTab));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const taskCard = event.target instanceof Element
|
|
65
|
+
? event.target.closest('[data-task-card]')
|
|
66
|
+
: null;
|
|
67
|
+
if (taskCard) {
|
|
68
|
+
openTaskCard(taskCard.dataset.taskCard);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
});
|
|
73
|
+
root?.addEventListener('input', (event) => {
|
|
74
|
+
const target = event.target instanceof Element
|
|
75
|
+
? event.target.closest('textarea[data-human-answer-code]')
|
|
76
|
+
: null;
|
|
77
|
+
if (!target)
|
|
78
|
+
return;
|
|
79
|
+
humanAnswerDrafts.set(target.dataset.humanAnswerCode ?? '', target.value);
|
|
80
|
+
});
|
|
81
|
+
root?.addEventListener('submit', (event) => {
|
|
82
|
+
const form = event.target instanceof Element
|
|
83
|
+
? event.target.closest('[data-human-answer-form]')
|
|
84
|
+
: null;
|
|
85
|
+
if (!form)
|
|
86
|
+
return;
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
void submitHumanActionAnswer(form);
|
|
89
|
+
});
|
|
90
|
+
root?.addEventListener('keydown', (event) => {
|
|
91
|
+
if (event.key === 'Escape' && selectedTaskCardId) {
|
|
92
|
+
closeTaskDialog();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const taskCard = event.target instanceof Element
|
|
96
|
+
? event.target.closest('[data-task-card]')
|
|
97
|
+
: null;
|
|
98
|
+
if (taskCard && (event.key === 'Enter' || event.key === ' ')) {
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
openTaskCard(taskCard.dataset.taskCard);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const target = event.target instanceof Element
|
|
104
|
+
? event.target.closest('[data-content-tab]')
|
|
105
|
+
: null;
|
|
106
|
+
if (!target || !isTabNavigationKey(event.key))
|
|
107
|
+
return;
|
|
108
|
+
const tabs = contentTabButtons();
|
|
109
|
+
const currentIndex = tabs.indexOf(target);
|
|
110
|
+
if (currentIndex < 0)
|
|
111
|
+
return;
|
|
112
|
+
event.preventDefault();
|
|
113
|
+
const nextIndex = nextTabIndex(event.key, currentIndex, tabs.length);
|
|
114
|
+
const nextTab = asContentTab(tabs[nextIndex]?.dataset.contentTab);
|
|
115
|
+
pendingFocusTab = nextTab;
|
|
116
|
+
selectContentTab(nextTab);
|
|
117
|
+
});
|
|
118
|
+
root?.addEventListener('focusin', (event) => {
|
|
119
|
+
const target = chartTooltipTarget(event.target);
|
|
120
|
+
if (target)
|
|
121
|
+
showChartPopover(target);
|
|
122
|
+
});
|
|
123
|
+
root?.addEventListener('focusout', (event) => {
|
|
124
|
+
const target = chartTooltipTarget(event.target);
|
|
125
|
+
if (target)
|
|
126
|
+
hideChartPopover();
|
|
127
|
+
});
|
|
128
|
+
root?.addEventListener('wheel', (event) => {
|
|
129
|
+
const target = event.target instanceof Element
|
|
130
|
+
? event.target.closest('.task-column-body, .task-board-column')
|
|
131
|
+
: null;
|
|
132
|
+
if (!target)
|
|
133
|
+
return;
|
|
134
|
+
const board = target.closest('.task-board-scroll');
|
|
135
|
+
if (!board)
|
|
136
|
+
return;
|
|
137
|
+
const horizontalDelta = event.deltaX || (event.shiftKey ? event.deltaY : 0);
|
|
138
|
+
if (!horizontalDelta || Math.abs(horizontalDelta) < Math.abs(event.deltaY))
|
|
139
|
+
return;
|
|
140
|
+
board.scrollLeft += horizontalDelta;
|
|
141
|
+
event.preventDefault();
|
|
142
|
+
}, { passive: false });
|
|
143
|
+
root?.addEventListener('pointerover', (event) => {
|
|
144
|
+
const target = nearestChartPopoverTarget(event);
|
|
145
|
+
if (!target) {
|
|
146
|
+
hideChartPopover();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
activeContributionTarget = target;
|
|
150
|
+
showChartPopover(target);
|
|
151
|
+
});
|
|
152
|
+
root?.addEventListener('mouseover', (event) => {
|
|
153
|
+
const target = nearestChartPopoverTarget(event);
|
|
154
|
+
if (!target) {
|
|
155
|
+
hideChartPopover();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
activeContributionTarget = target;
|
|
159
|
+
showChartPopover(target);
|
|
160
|
+
});
|
|
161
|
+
root?.addEventListener('pointermove', (event) => {
|
|
162
|
+
const target = nearestChartPopoverTarget(event);
|
|
163
|
+
if (!target) {
|
|
164
|
+
hideChartPopover();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
activeContributionTarget = target;
|
|
168
|
+
showChartPopover(target, event.clientX, event.clientY);
|
|
169
|
+
});
|
|
170
|
+
root?.addEventListener('mousemove', (event) => {
|
|
171
|
+
const target = nearestChartPopoverTarget(event);
|
|
172
|
+
if (!target) {
|
|
173
|
+
hideChartPopover();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
activeContributionTarget = target;
|
|
177
|
+
showChartPopover(target, event.clientX, event.clientY);
|
|
178
|
+
});
|
|
179
|
+
root?.addEventListener('pointerout', (event) => {
|
|
180
|
+
if (isStillInsideContributionGrid(event))
|
|
181
|
+
return;
|
|
182
|
+
hideChartPopover();
|
|
183
|
+
});
|
|
184
|
+
root?.addEventListener('mouseout', (event) => {
|
|
185
|
+
if (isStillInsideContributionGrid(event))
|
|
186
|
+
return;
|
|
187
|
+
hideChartPopover();
|
|
188
|
+
});
|
|
189
|
+
window.addEventListener('hashchange', () => {
|
|
190
|
+
const route = routeStateFromLocation();
|
|
191
|
+
selectedTab = route.tab;
|
|
192
|
+
selectedTaskCardId = route.taskId;
|
|
193
|
+
selectedTicketId = route.ticket;
|
|
194
|
+
if (currentDashboard)
|
|
195
|
+
renderDashboard(currentDashboard);
|
|
196
|
+
});
|
|
197
|
+
void refresh();
|
|
198
|
+
const dashboardStreamConnected = connectDashboardStream();
|
|
199
|
+
window.setInterval(refresh, dashboardStreamConnected ? 30000 : 2000);
|
|
200
|
+
function connectDashboardStream() {
|
|
201
|
+
if (!('EventSource' in window))
|
|
202
|
+
return false;
|
|
203
|
+
const source = new EventSource('/api/dashboard/stream');
|
|
204
|
+
source.addEventListener('message', (event) => {
|
|
205
|
+
try {
|
|
206
|
+
const dashboard = JSON.parse(event.data);
|
|
207
|
+
currentDashboard = dashboard;
|
|
208
|
+
const signature = dashboardSignature(dashboard);
|
|
209
|
+
if (root?.hasChildNodes() && signature === renderedDashboardSignature)
|
|
210
|
+
return;
|
|
211
|
+
renderDashboard(dashboard, signature);
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
void refresh();
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
source.addEventListener('error', () => {
|
|
218
|
+
source.close();
|
|
219
|
+
window.setTimeout(connectDashboardStream, 2000);
|
|
220
|
+
});
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
async function refresh() {
|
|
224
|
+
let dashboard;
|
|
225
|
+
try {
|
|
226
|
+
const response = await fetch('/api/dashboard');
|
|
227
|
+
if (!response.ok)
|
|
228
|
+
throw new Error(`dashboard fetch failed: ${response.status}`);
|
|
229
|
+
dashboard = await response.json();
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
dashboard = offlineDashboard(error);
|
|
233
|
+
}
|
|
234
|
+
currentDashboard = dashboard;
|
|
235
|
+
const signature = dashboardSignature(dashboard);
|
|
236
|
+
if (root?.hasChildNodes() && signature === renderedDashboardSignature)
|
|
237
|
+
return;
|
|
238
|
+
renderDashboard(dashboard, signature);
|
|
239
|
+
}
|
|
240
|
+
function renderDashboard(dashboard, signature = dashboardSignature(dashboard)) {
|
|
241
|
+
hideChartPopover();
|
|
242
|
+
captureScrollPositions();
|
|
243
|
+
pruneResolvedHumanActionDrafts(dashboard);
|
|
244
|
+
const lanes = laneRollups(dashboard);
|
|
245
|
+
root?.replaceChildren(_jsx(DashboardView, { dashboard: dashboard, lanes: lanes }));
|
|
246
|
+
renderedDashboardSignature = signature;
|
|
247
|
+
restoreScrollPositions();
|
|
248
|
+
}
|
|
249
|
+
function dashboardSignature(dashboard) {
|
|
250
|
+
return JSON.stringify({
|
|
251
|
+
ok: dashboard.ok,
|
|
252
|
+
cwd: dashboard.cwd,
|
|
253
|
+
summary: dashboard.summary,
|
|
254
|
+
health: dashboard.health,
|
|
255
|
+
quality: dashboard.quality,
|
|
256
|
+
timeSeries: dashboard.timeSeries,
|
|
257
|
+
lanes: dashboard.lanes,
|
|
258
|
+
jobs: dashboard.jobs,
|
|
259
|
+
humanActions: dashboard.humanActions,
|
|
260
|
+
humanActionAnswers: dashboard.humanActionAnswers,
|
|
261
|
+
events: dashboard.events,
|
|
262
|
+
routing: dashboard.routing,
|
|
263
|
+
backlog: dashboard.backlog,
|
|
264
|
+
semantic: dashboard.semantic,
|
|
265
|
+
sources: dashboard.sources
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
function DashboardView({ dashboard, lanes }) {
|
|
269
|
+
const attention = attentionSummary(dashboard.jobs);
|
|
270
|
+
const sourceEntries = sourceRows(dashboard);
|
|
271
|
+
const visibleJobs = dashboard.jobs;
|
|
272
|
+
const visibleEvents = dashboard.events;
|
|
273
|
+
const audit = auditSummary(dashboard, visibleJobs);
|
|
274
|
+
const success = successSummary(dashboard, visibleJobs);
|
|
275
|
+
const selectedTask = selectedTaskItem(dashboard, visibleJobs);
|
|
276
|
+
if (selectedTask)
|
|
277
|
+
void fetchTaskDetails(selectedTask);
|
|
278
|
+
return _jsxs("main", { className: "shell", children: [_jsx("section", { className: "operator-grid", children: _jsxs("section", { className: "operator-main", children: [_jsx("nav", { className: "content-tabs", role: "tablist", "aria-label": "Dashboard sections", children: contentTabs.map((tab) => contentTab(tab.id, tab.label, tabMeta(tab.id, {
|
|
279
|
+
jobs: visibleJobs.length,
|
|
280
|
+
questions: humanActionRows(visibleJobs, audit, dashboard).length,
|
|
281
|
+
events: visibleEvents.length,
|
|
282
|
+
sources: sourceEntries.length
|
|
283
|
+
}))) }), _jsx("section", { id: `${selectedTab}-panel`, className: "content-shell", "data-scroll-id": `content-${selectedTab}`, role: "tabpanel", "aria-labelledby": `${selectedTab}-tab`, children: contentPanel(selectedTab, {
|
|
284
|
+
dashboard,
|
|
285
|
+
lanes,
|
|
286
|
+
sourceEntries,
|
|
287
|
+
visibleJobs,
|
|
288
|
+
visibleEvents,
|
|
289
|
+
attention,
|
|
290
|
+
audit,
|
|
291
|
+
success
|
|
292
|
+
}) })] }) }), selectedTask ? _jsx(TaskDetailDialog, { job: selectedTask, dashboard: dashboard }) : null] });
|
|
293
|
+
}
|
|
294
|
+
function contentPanel(tab, input) {
|
|
295
|
+
const boardItems = taskBoardItems(input.dashboard, input.visibleJobs);
|
|
296
|
+
if (tab === 'work')
|
|
297
|
+
return _jsx(Panel, { title: "Overview", meta: `${text(input.visibleJobs.length)} tasks`, hideHead: true, children: _jsx(WorkOverview, { dashboard: input.dashboard, lanes: input.lanes, jobs: input.visibleJobs, attention: input.attention, audit: input.audit, success: input.success }) });
|
|
298
|
+
if (tab === 'board')
|
|
299
|
+
return _jsx(Panel, { title: "Task board", meta: `${text(boardItems.length)} active-visible tasks`, children: _jsx(TaskBoard, { dashboard: input.dashboard, jobs: input.visibleJobs }) });
|
|
300
|
+
if (tab === 'swarm') {
|
|
301
|
+
const sourceKind = dashboardSourceKind(input.dashboard);
|
|
302
|
+
const activeCount = activeAgentTaskCount(input.dashboard, input.visibleJobs);
|
|
303
|
+
return _jsx(Panel, { title: sourceKind === 'demo' ? 'Demo agents' : 'Active agents', meta: sourceKind === 'demo' ? 'fixture examples' : activeCount ? `${text(activeCount)} running` : 'none running', children: _jsx(AgentWork, { dashboard: input.dashboard, jobs: input.visibleJobs }) });
|
|
304
|
+
}
|
|
305
|
+
if (tab === 'performance')
|
|
306
|
+
return _jsx(Panel, { title: "Performance", meta: performanceTabMeta(input.dashboard, input.visibleJobs), children: _jsx(PerformanceView, { dashboard: input.dashboard, jobs: input.visibleJobs, attention: input.attention, audit: input.audit }) });
|
|
307
|
+
if (tab === 'history')
|
|
308
|
+
return _jsx(Panel, { title: "History", meta: historyTabMeta(input.dashboard, input.visibleJobs, input.success), children: _jsx(HistoryView, { dashboard: input.dashboard, jobs: input.visibleJobs, events: input.visibleEvents, success: input.success, attention: input.attention }) });
|
|
309
|
+
if (tab === 'testing')
|
|
310
|
+
return _jsx(Panel, { title: "Testing", meta: testingTabMeta(input.visibleJobs), children: _jsx(TestingView, { jobs: input.visibleJobs, events: input.visibleEvents }) });
|
|
311
|
+
return _jsx(Panel, { title: "Questions for you", meta: `${text(humanActionRows(input.visibleJobs, input.audit, input.dashboard).length)} open`, children: _jsx(HumanActionQueue, { dashboard: input.dashboard, jobs: input.visibleJobs, audit: input.audit }) });
|
|
312
|
+
}
|
|
313
|
+
function Panel({ title, meta, children, className, hideHead = false }) {
|
|
314
|
+
return _jsxs("section", { className: className ? `panel ${className}` : 'panel', children: [hideHead ? null : _jsxs("div", { className: "panel-head", children: [_jsx("h2", { children: title }), meta ? _jsx("span", { children: meta }) : null] }), children] });
|
|
315
|
+
}
|
|
316
|
+
function contentTab(id, label, meta) {
|
|
317
|
+
const active = selectedTab === id;
|
|
318
|
+
return _jsxs("button", { id: `${id}-tab`, type: "button", className: active ? 'content-tab active' : 'content-tab', "data-content-tab": id, role: "tab", "aria-selected": active ? 'true' : 'false', "aria-controls": `${id}-panel`, tabIndex: active ? 0 : -1, children: [_jsx("span", { children: label }), _jsx("small", { children: meta })] });
|
|
319
|
+
}
|
|
320
|
+
function WorkOverview({ dashboard, lanes, jobs, attention, audit, success }) {
|
|
321
|
+
const health = dashboardHealthSummary(dashboard, jobs, attention);
|
|
322
|
+
const contribution = contributionGrid(dashboard, jobs, dashboard.events);
|
|
323
|
+
const successfulJobCount = successLikeJobCount(jobs);
|
|
324
|
+
const progressRatio = health.jobCount ? successfulJobCount / health.jobCount : 0;
|
|
325
|
+
const progressLabel = formatPercent(progressRatio);
|
|
326
|
+
const progressWidth = Math.max(0, Math.min(100, progressRatio * 100));
|
|
327
|
+
const cost = workCostSummary(dashboard, jobs);
|
|
328
|
+
void lanes;
|
|
329
|
+
void audit;
|
|
330
|
+
void success;
|
|
331
|
+
return _jsxs("div", { className: "work-layout", "data-scroll-id": "work", children: [_jsxs("section", { className: "goal-card", children: [_jsxs("div", { children: [_jsx("span", { children: "Goal" }), _jsx("h3", { children: currentGoalTitle(dashboard) }), _jsx("p", { children: goalProgressText(health, successfulJobCount) })] }), _jsxs("div", { className: "goal-progress", children: [_jsxs("div", { className: "goal-progress-head", children: [_jsx("b", { children: progressLabel }), _jsxs("span", { children: [text(successfulJobCount), " of ", text(health.jobCount), " tasks completed successfully"] })] }), _jsx("div", { className: "goal-progress-track", role: "img", "aria-label": `${progressLabel} successful task progress`, children: _jsx("span", { className: `goal-progress-fill ${health.tone}`, style: `width:${progressWidth}%` }) })] })] }), _jsxs("section", { className: "work-section work-contribution-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Progress by day" }), _jsxs("span", { children: [text(contribution.year), " \u00B7 January through December"] })] }), _jsx(ContributionGraph, { grid: contribution, prominent: true })] }), _jsxs("section", { className: "work-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Token cost estimate" }), _jsx("span", { children: cost.detail })] }), _jsxs("div", { className: "work-cost-metrics", children: [costMetric('Total input', cost.totalInput, cost.totalDetail), costMetric('Uncached input', cost.freshInput, cost.freshDetail), costMetric('Cached input', cost.cachedInput, cost.cachedDetail), costMetric('Budget warnings', cost.budgetWarnings, cost.budgetDetail)] })] })] });
|
|
332
|
+
}
|
|
333
|
+
function currentGoalTitle(dashboard) {
|
|
334
|
+
const backlog = recordValue(dashboard.backlog);
|
|
335
|
+
const explicit = textValue(backlog.title ?? backlog.goal ?? backlog.objective ?? backlog.summary, '');
|
|
336
|
+
if (explicit)
|
|
337
|
+
return explicit;
|
|
338
|
+
const id = textValue(backlog.id, '');
|
|
339
|
+
if (id && id !== 'backlog' && id !== 'next-backlog')
|
|
340
|
+
return sentenceCaseIdentifier(id);
|
|
341
|
+
return 'Rewrite the Frontier swarm dashboard';
|
|
342
|
+
}
|
|
343
|
+
function sentenceCaseIdentifier(value) {
|
|
344
|
+
const spaced = value
|
|
345
|
+
.replace(/[_-]+/g, ' ')
|
|
346
|
+
.replace(/\s+/g, ' ')
|
|
347
|
+
.trim();
|
|
348
|
+
if (!spaced)
|
|
349
|
+
return value;
|
|
350
|
+
return spaced[0].toUpperCase() + spaced.slice(1);
|
|
351
|
+
}
|
|
352
|
+
function goalProgressText(health, successfulJobCount) {
|
|
353
|
+
if (!health.jobCount)
|
|
354
|
+
return 'No active swarm work is loaded yet.';
|
|
355
|
+
const attentionCount = health.failedJobCount + health.blockedJobCount + health.warningJobCount;
|
|
356
|
+
if (successfulJobCount >= health.jobCount)
|
|
357
|
+
return 'Every tracked task has completed successfully.';
|
|
358
|
+
if (health.runningJobCount) {
|
|
359
|
+
return `The swarm is working through ${text(health.jobCount)} tracked tasks. ${text(health.runningJobCount)} are still running.`;
|
|
360
|
+
}
|
|
361
|
+
if (health.terminalJobCount >= health.jobCount) {
|
|
362
|
+
return `Execution has finished, but ${text(attentionCount)} tasks still need review or fixes.`;
|
|
363
|
+
}
|
|
364
|
+
return `No agents are running right now. ${text(Math.max(0, health.jobCount - health.terminalJobCount))} tasks remain queued and ${text(attentionCount)} need review or fixes.`;
|
|
365
|
+
}
|
|
366
|
+
function workCostSummary(dashboard, jobs) {
|
|
367
|
+
const telemetry = tokenTimeSummary(dashboard, jobs);
|
|
368
|
+
const context = contextPressureSummary(jobs);
|
|
369
|
+
return {
|
|
370
|
+
totalInput: telemetry.tokenValue,
|
|
371
|
+
totalDetail: telemetry.tokenDetail,
|
|
372
|
+
freshInput: context.uncachedInputTokens ? formatNumber(context.uncachedInputTokens) : '-',
|
|
373
|
+
freshDetail: context.estimatedInputTokens ? `${formatRatio(context.uncachedRatio)} of estimate` : 'uncached model input',
|
|
374
|
+
cachedInput: context.cachedInputTokens ? formatNumber(context.cachedInputTokens) : '-',
|
|
375
|
+
cachedDetail: context.actualInputTokens ? `${formatRatio(context.cachedInputTokens / Math.max(1, context.actualInputTokens))} of reported` : 'reported cache reuse',
|
|
376
|
+
budgetWarnings: formatNumber(context.warningCount + context.failedCount),
|
|
377
|
+
budgetDetail: `${formatNumber(context.failedCount)} failed budget`,
|
|
378
|
+
detail: telemetry.durationValue === '-' ? 'token estimates and budget pressure' : `${telemetry.durationValue} runtime estimate`
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function costMetric(label, value, detail) {
|
|
382
|
+
return _jsxs("article", { className: "cost-metric", children: [_jsx("span", { children: label }), _jsx("b", { children: value }), _jsx("small", { children: detail })] });
|
|
383
|
+
}
|
|
384
|
+
function TaskBoard({ dashboard, jobs }) {
|
|
385
|
+
const items = taskBoardItems(dashboard, jobs);
|
|
386
|
+
const columns = taskBoardColumns(items);
|
|
387
|
+
const activeCount = columns.reduce((sum, column) => column.id === 'done' ? sum : sum + column.items.length, 0);
|
|
388
|
+
return _jsx("div", { className: "task-board-layout", "data-scroll-id": "task-board", children: _jsx("div", { className: "task-board-scroll", "data-scroll-id": "task-board-x", "aria-label": `${text(items.length)} AI-managed tasks, ${text(activeCount)} not done`, children: columns.map((column) => _jsxs("section", { className: `task-board-column ${column.id}`, "aria-label": `${column.title}: ${text(column.items.length)} tasks`, children: [_jsxs("header", { className: "task-column-head", children: [_jsxs("div", { children: [_jsx("h3", { children: column.title }), _jsx("span", { children: column.detail })] }), _jsx("b", { children: text(column.items.length) })] }), _jsx("div", { className: "task-column-body", "data-scroll-id": `task-column-${column.id}`, children: column.items.length
|
|
389
|
+
? column.items.map((job) => _jsx(TaskBoardCard, { job: job }))
|
|
390
|
+
: _jsx("p", { className: "empty tight", children: column.empty }) })] })) }) });
|
|
391
|
+
}
|
|
392
|
+
function AgentWork({ dashboard, jobs }) {
|
|
393
|
+
const items = taskBoardItems(dashboard, jobs).filter(isActiveAgentJob);
|
|
394
|
+
const workers = agentWorkers(items);
|
|
395
|
+
void dashboard;
|
|
396
|
+
return _jsx("div", { className: "agent-work-layout", "data-scroll-id": "swarm", children: _jsxs("section", { className: "agent-roster-panel", "aria-label": "Active agent roster", children: [_jsxs("div", { className: "agent-roster-head", children: [_jsx("span", { children: "Agent" }), _jsx("span", { children: "Current work" }), _jsx("span", { children: "Model" }), _jsx("span", { children: "Run time" }), _jsx("span", { children: "Input" }), _jsx("span", { children: "Files" })] }), _jsx("div", { className: "agent-roster-list", "data-scroll-id": "swarm-roster", children: workers.length
|
|
397
|
+
? workers.map((worker, index) => _jsx(AgentWorkerCard, { worker: worker, index: index, now: timeValue(dashboard.generatedAt) ?? Date.now() }))
|
|
398
|
+
: _jsx("p", { className: "agent-roster-empty", children: "No active agents are running right now." }) })] }) });
|
|
399
|
+
}
|
|
400
|
+
function AgentWorkerCard({ worker, index, now }) {
|
|
401
|
+
const model = agentModelSummary(worker);
|
|
402
|
+
const files = agentTouchedFiles(worker);
|
|
403
|
+
const evidenceCount = agentEvidenceCount(worker);
|
|
404
|
+
const visibleJobs = worker.currentJobs.slice(0, 2);
|
|
405
|
+
const hiddenJobCount = Math.max(0, worker.currentJobs.length - visibleJobs.length);
|
|
406
|
+
return _jsxs("article", { className: `agent-worker-card ${worker.status}`, style: `--agent-color:${worker.color}`, draggable: "false", "aria-label": `${agentDisplayName(worker, index)} assigned to ${text(worker.currentJobs.length)} current tasks`, children: [_jsxs("div", { className: "agent-cell agent-cell-agent", children: [_jsx("span", { className: "agent-status-dot", "aria-hidden": "true" }), _jsxs("div", { children: [_jsx("b", { children: agentDisplayName(worker, index) }), _jsx("small", { children: agentStatusLabel(worker.status) })] })] }), _jsx("div", { className: "agent-cell agent-cell-task", children: _jsxs("div", { className: "agent-task-list", children: [visibleJobs.map((job) => _jsxs("button", { type: "button", className: "agent-task-row", "data-task-card": taskCardId(job), children: [_jsx("code", { children: ticketId(job) }), _jsx("span", { children: taskTitle(job) }), _jsx("small", { children: jobRuntimeLabel(job, now) })] })), hiddenJobCount ? _jsxs("small", { className: "agent-overflow-note", children: ["+", text(hiddenJobCount), " more active ", hiddenJobCount === 1 ? 'ticket' : 'tickets'] }) : null] }) }), _jsx("div", { className: "agent-cell agent-cell-model", children: _jsx("span", { className: "agent-model-pill", children: _jsx("span", { children: model }) }) }), _jsxs("div", { className: "agent-cell agent-cell-runtime", children: [_jsx("b", { children: agentRuntimeValue(worker, now) }), _jsx("small", { children: worker.currentJobs.length === 1 ? 'current ticket' : `${text(worker.currentJobs.length)} tickets` })] }), _jsxs("div", { className: "agent-cell agent-cell-cost", children: [_jsx("b", { children: agentTokenValue(worker) }), _jsxs("small", { children: [agentUncachedTokenValue(worker), " uncached"] })] }), _jsxs("div", { className: "agent-cell agent-cell-files", children: [_jsxs("div", { className: "agent-file-list", children: [files.slice(0, 2).map((file) => _jsx(ArtifactLink, { path: file, label: artifactLabel(file), className: "agent-file-tag" })), files.length > 2 ? _jsxs("span", { className: "agent-file-more", children: ["+", text(files.length - 2)] }) : null, !files.length ? _jsx("span", { className: "agent-file-empty", children: "No files" }) : null] }), evidenceCount ? _jsxs("small", { children: [text(evidenceCount), " ", evidenceCount === 1 ? 'artifact' : 'artifacts'] }) : null] })] });
|
|
407
|
+
}
|
|
408
|
+
function TaskBoardCard({ job }) {
|
|
409
|
+
const id = taskCardId(job);
|
|
410
|
+
const ticket = ticketId(job);
|
|
411
|
+
const risk = jobRisk(job);
|
|
412
|
+
return _jsxs("article", { className: `task-card ${risk}`, "data-task-card": id, role: "button", tabIndex: 0, draggable: "false", "aria-label": `Open task ${taskTitle(job)}`, children: [_jsxs("div", { className: "task-card-top", children: [_jsx("code", { className: "task-ticket", children: ticket }), _jsx("button", { type: "button", className: "task-id-copy", "data-copy-code": ticket, title: `Copy ${ticket}`, children: "Copy" })] }), _jsx("span", { className: "task-card-kicker", children: taskCardStatus(job) }), _jsx("b", { children: taskTitle(job) }), _jsxs("small", { children: [laneOf(job), " \u00B7 ", taskStatusDetail(job)] }), _jsxs("div", { className: "task-card-foot", children: [_jsxs("span", { children: [formatNumber(numberValue(job.changedPathCount)), " paths"] }), _jsxs("span", { children: [formatNumber(numberValue(job.evidencePathCount)), " evidence"] })] })] });
|
|
413
|
+
}
|
|
414
|
+
function TaskDetailDialog({ job, dashboard }) {
|
|
415
|
+
const title = taskTitle(job);
|
|
416
|
+
const reasons = taskReasonItems(job);
|
|
417
|
+
const ticket = ticketId(job);
|
|
418
|
+
const id = taskCardId(job);
|
|
419
|
+
const details = taskDetailsCache.get(id);
|
|
420
|
+
const tokenSummary = taskTokenSummary(job);
|
|
421
|
+
void dashboard;
|
|
422
|
+
return _jsx("div", { className: "task-modal-backdrop", "data-modal-backdrop": "true", children: _jsxs("section", { className: `task-dialog ${jobRisk(job)}`, role: "dialog", "aria-modal": "true", "aria-labelledby": "task-dialog-title", children: [_jsxs("header", { className: "task-dialog-head", children: [_jsxs("div", { children: [_jsx("span", { children: taskCardStatus(job) }), _jsx("h3", { id: "task-dialog-title", children: title }), _jsx("p", { children: taskDetailSummary(job) })] }), _jsxs("div", { className: "task-dialog-actions", children: [_jsx("button", { type: "button", "data-copy-code": ticket, children: "Copy ID" }), _jsx("button", { type: "button", "data-modal-close": "true", "aria-label": "Close task details", children: "Close" })] })] }), _jsxs("div", { className: "task-dialog-body", "data-scroll-id": taskDialogScrollId(id), children: [_jsxs("section", { className: "task-dialog-grid", "aria-label": "Task facts", children: [taskFact('Ticket', ticket), taskFact('Task', text(job.taskId ?? job.id)), taskFact('Lane', laneOf(job)), taskFact('Status', text(job.status)), taskFact('Column', taskBoardColumnTitle(taskBoardColumnId(job))), taskFact('Merge', text(job.mergeReadiness ?? job.disposition ?? job.bucket)), taskFact('Cost', tokenSummary)] }), _jsxs("section", { className: "task-dialog-section", children: [_jsx("h4", { children: "Details" }), _jsx("p", { children: taskMarkdownSummary(job) })] }), _jsxs("section", { className: "task-dialog-section split", children: [_jsxs("div", { children: [_jsx("h4", { children: "Reasons" }), reasons.length ? _jsx("ul", { children: reasons.map((reason) => _jsx("li", { children: reason })) }) : _jsx("p", { className: "empty tight", children: "No reasons reported." })] }), _jsxs("div", { children: [_jsx("h4", { children: "Output" }), _jsxs("ul", { children: [_jsxs("li", { children: [formatNumber(numberValue(job.changedPathCount)), " changed paths reported."] }), _jsxs("li", { children: [formatNumber(numberValue(job.evidencePathCount)), " evidence artifacts reported."] }), _jsxs("li", { children: [pathSummaryText(job), "."] })] })] })] }), _jsx(TaskFileDiffs, { job: job, details: details }), _jsx(TaskResultDetails, { job: job, details: details })] })] }) });
|
|
423
|
+
}
|
|
424
|
+
function TaskFileDiffs({ job, details }) {
|
|
425
|
+
const changedPaths = stringArray(job.changedPaths).slice(0, 12);
|
|
426
|
+
if (!details && job.boardKind !== 'backlog')
|
|
427
|
+
return _jsxs("section", { className: "task-dialog-section", children: [_jsx("h4", { children: "Files changed" }), _jsx("p", { className: "empty tight", children: "Loading file diffs..." })] });
|
|
428
|
+
if (details?.files?.length)
|
|
429
|
+
return _jsxs("section", { className: "task-dialog-section", children: [_jsx("h4", { children: "Files changed" }), _jsx("div", { className: "task-file-list", children: details.files.map((file) => _jsxs("details", { className: "task-file-diff", children: [_jsxs("summary", { children: [_jsx("span", { className: "task-file-name", children: file.path }), _jsxs("small", { children: ["+", text(file.additions), " -", text(file.deletions), file.truncated ? ' · truncated' : ''] }), _jsx(ArtifactLink, { path: file.artifactPath ?? file.path, label: "Reveal", className: "task-file-reveal" })] }), _jsx(DiffRenderer, { file: file })] })) })] });
|
|
430
|
+
if (changedPaths.length)
|
|
431
|
+
return _jsxs("section", { className: "task-dialog-section", children: [_jsx("h4", { children: "Files changed" }), _jsx("p", { className: "empty tight", children: details?.error ? `Diff unavailable: ${details.error}` : 'No patch diff was available in the collected evidence.' }), _jsx(ArtifactPathList, { paths: changedPaths })] });
|
|
432
|
+
return _jsxs("section", { className: "task-dialog-section", children: [_jsx("h4", { children: "Files changed" }), _jsx("p", { className: "empty tight", children: job.boardKind === 'backlog' ? 'No files yet. This item has not produced an implementation patch.' : 'No changed files reported.' })] });
|
|
433
|
+
}
|
|
434
|
+
function TaskResultDetails({ job, details }) {
|
|
435
|
+
const evidenceArtifacts = artifactListFromDetails(job, details).slice(0, 10);
|
|
436
|
+
const passed = details?.commandsPassed ?? [];
|
|
437
|
+
const failed = details?.commandsFailed ?? [];
|
|
438
|
+
if (passed.length || failed.length)
|
|
439
|
+
return _jsxs("section", { className: "task-dialog-section", children: [_jsx("h4", { children: "Results" }), _jsxs("div", { className: "task-result-list", children: [failed.map((entry) => taskResultRow(entry, 'failed')), passed.map((entry) => taskResultRow(entry, 'passed'))] }), evidenceArtifacts.length ? _jsxs("div", { className: "task-section-subblock", children: [_jsx("h5", { children: "Evidence" }), _jsx(ArtifactList, { artifacts: evidenceArtifacts })] }) : null] });
|
|
440
|
+
if (!evidenceArtifacts.length)
|
|
441
|
+
return _jsxs("section", { className: "task-dialog-section", children: [_jsx("h4", { children: "Results" }), _jsx("p", { className: "empty tight", children: "No test or evidence output is attached to this task yet." })] });
|
|
442
|
+
return _jsxs("section", { className: "task-dialog-section", children: [_jsx("h4", { children: "Evidence" }), _jsx(ArtifactList, { artifacts: evidenceArtifacts })] });
|
|
443
|
+
}
|
|
444
|
+
function DiffRenderer({ file }) {
|
|
445
|
+
const hunks = file.hunks?.length ? file.hunks : fallbackDiffHunks(file.diff);
|
|
446
|
+
return _jsx("div", { className: `diff-renderer language-${file.language ?? 'text'}`, "data-diff-renderer": "true", children: hunks.map((hunk) => _jsx("section", { className: "diff-hunk", "aria-label": hunk.header, children: hunk.lines.map((line) => diffLineNode(line, file.language ?? 'text')) })) });
|
|
447
|
+
}
|
|
448
|
+
function diffLineNode(line, language) {
|
|
449
|
+
const prefix = line.kind === 'add' ? '+' : line.kind === 'delete' ? '-' : line.kind === 'context' ? ' ' : '';
|
|
450
|
+
return _jsxs("div", { className: `diff-line diff-line-${line.kind}`, children: [_jsx("span", { className: "diff-line-old", children: line.oldLine === undefined ? '' : text(line.oldLine) }), _jsx("span", { className: "diff-line-new", children: line.newLine === undefined ? '' : text(line.newLine) }), _jsx("span", { className: "diff-line-prefix", children: prefix }), _jsx("code", { className: "diff-line-code", children: line.kind === 'add' || line.kind === 'delete' || line.kind === 'context'
|
|
451
|
+
? highlightCode(line.content, language)
|
|
452
|
+
: line.content })] });
|
|
453
|
+
}
|
|
454
|
+
function fallbackDiffHunks(diff) {
|
|
455
|
+
return [{
|
|
456
|
+
header: 'Raw patch',
|
|
457
|
+
lines: diff.split('\n').map((line) => {
|
|
458
|
+
if (line.startsWith('+') && !line.startsWith('+++'))
|
|
459
|
+
return { kind: 'add', content: line.slice(1) };
|
|
460
|
+
if (line.startsWith('-') && !line.startsWith('---'))
|
|
461
|
+
return { kind: 'delete', content: line.slice(1) };
|
|
462
|
+
if (line.startsWith('@@'))
|
|
463
|
+
return { kind: 'hunk', content: line };
|
|
464
|
+
if (line.startsWith(' '))
|
|
465
|
+
return { kind: 'context', content: line.slice(1) };
|
|
466
|
+
return { kind: 'meta', content: line };
|
|
467
|
+
})
|
|
468
|
+
}];
|
|
469
|
+
}
|
|
470
|
+
function ArtifactPathList({ paths }) {
|
|
471
|
+
return _jsx("div", { className: "task-path-list", children: paths.map((entry) => _jsx(ArtifactLink, { path: entry, label: entry })) });
|
|
472
|
+
}
|
|
473
|
+
function ArtifactList({ artifacts }) {
|
|
474
|
+
return _jsx("div", { className: "task-path-list", children: artifacts.map((artifact) => _jsx(ArtifactLink, { path: artifact.path, label: artifact.label || artifact.path })) });
|
|
475
|
+
}
|
|
476
|
+
function ArtifactLink({ path, label, className }) {
|
|
477
|
+
return _jsx("button", { type: "button", className: className ? `artifact-link ${className}` : 'artifact-link', "data-reveal-artifact-path": path, title: `Reveal ${path} in Finder`, children: label });
|
|
478
|
+
}
|
|
479
|
+
function artifactListFromDetails(job, details) {
|
|
480
|
+
const detailArtifacts = details?.evidenceArtifacts ?? [];
|
|
481
|
+
const legacy = stringArray(job.evidencePaths).map((entry) => ({ path: entry, label: artifactLabel(entry) }));
|
|
482
|
+
const byPath = new Map();
|
|
483
|
+
for (const artifact of [...detailArtifacts, ...legacy])
|
|
484
|
+
byPath.set(artifact.path, artifact);
|
|
485
|
+
return Array.from(byPath.values());
|
|
486
|
+
}
|
|
487
|
+
function highlightCode(value, language) {
|
|
488
|
+
if (!shouldHighlightLanguage(language) || !value)
|
|
489
|
+
return [value];
|
|
490
|
+
const pattern = /(\/\/.*$|\/\*.*?\*\/|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`|\b(?:async|await|break|case|catch|class|const|continue|default|else|export|extends|false|for|from|function|if|import|interface|let|new|null|of|return|switch|true|try|type|undefined|var|while)\b|\b\d+(?:\.\d+)?\b)/g;
|
|
491
|
+
const out = [];
|
|
492
|
+
let lastIndex = 0;
|
|
493
|
+
for (const match of value.matchAll(pattern)) {
|
|
494
|
+
const token = match[0];
|
|
495
|
+
const index = match.index ?? 0;
|
|
496
|
+
if (index > lastIndex)
|
|
497
|
+
out.push(value.slice(lastIndex, index));
|
|
498
|
+
out.push(_jsx("span", { className: `diff-token ${tokenClass(token)}`, children: token }));
|
|
499
|
+
lastIndex = index + token.length;
|
|
500
|
+
}
|
|
501
|
+
if (lastIndex < value.length)
|
|
502
|
+
out.push(value.slice(lastIndex));
|
|
503
|
+
return out.length ? out : [value];
|
|
504
|
+
}
|
|
505
|
+
function shouldHighlightLanguage(language) {
|
|
506
|
+
return ['typescript', 'javascript', 'json', 'css', 'html', 'markdown'].includes(language);
|
|
507
|
+
}
|
|
508
|
+
function tokenClass(token) {
|
|
509
|
+
if (token.startsWith('//') || token.startsWith('/*'))
|
|
510
|
+
return 'comment';
|
|
511
|
+
if (token.startsWith('"') || token.startsWith("'") || token.startsWith('`'))
|
|
512
|
+
return 'string';
|
|
513
|
+
if (/^\d/.test(token))
|
|
514
|
+
return 'number';
|
|
515
|
+
return 'keyword';
|
|
516
|
+
}
|
|
517
|
+
function artifactLabel(path) {
|
|
518
|
+
const clean = path.replace(/\/+$/g, '');
|
|
519
|
+
return clean.split(/[\\/]/g).filter(Boolean).pop() ?? clean;
|
|
520
|
+
}
|
|
521
|
+
function taskResultRow(entry, state) {
|
|
522
|
+
const command = textValue(entry.command ?? entry.cmd ?? entry.name ?? entry.label, state === 'passed' ? 'passed command' : 'failed command');
|
|
523
|
+
const detail = textValue(entry.summary ?? entry.output ?? entry.error ?? entry.exitCode, '');
|
|
524
|
+
return _jsxs("article", { className: `task-result-row ${state}`, children: [_jsx("span", { children: state }), _jsx("b", { children: command }), detail ? _jsx("small", { children: detail }) : null] });
|
|
525
|
+
}
|
|
526
|
+
function taskBoardItems(dashboard, jobs) {
|
|
527
|
+
return [
|
|
528
|
+
...jobs.filter(isTaskBoardVisibleJob).map((job) => ({ ...job, boardKind: 'job' })),
|
|
529
|
+
...backlogBoardItems(dashboard)
|
|
530
|
+
];
|
|
531
|
+
}
|
|
532
|
+
function isTaskBoardVisibleJob(job) {
|
|
533
|
+
return !isResolvedCoordinatorReviewJob(job);
|
|
534
|
+
}
|
|
535
|
+
function isResolvedCoordinatorReviewJob(job) {
|
|
536
|
+
const bucket = normalized(job.bucket);
|
|
537
|
+
const status = coordinatorDecisionStatus(job);
|
|
538
|
+
if (bucket === 'review-resolved' || bucket === 'resolved-review' || job.reviewResolved === true)
|
|
539
|
+
return true;
|
|
540
|
+
return Boolean(status) && isResolvedCoordinatorDecisionStatus(status);
|
|
541
|
+
}
|
|
542
|
+
function isResolvedCoordinatorDecisionStatus(status) {
|
|
543
|
+
const value = normalized(status);
|
|
544
|
+
return Boolean(value) && !['open', 'pending', 'deferred', 'needs-review'].includes(value);
|
|
545
|
+
}
|
|
546
|
+
function activeAgentTaskCount(dashboard, jobs) {
|
|
547
|
+
return taskBoardItems(dashboard, jobs).filter(isActiveAgentJob).length;
|
|
548
|
+
}
|
|
549
|
+
function backlogBoardItems(dashboard) {
|
|
550
|
+
const direct = recordValue(dashboard.backlog);
|
|
551
|
+
const raw = recordValue(dashboard.raw);
|
|
552
|
+
const continuation = recordValue(raw.continuation);
|
|
553
|
+
const nextBacklog = recordValue(continuation.nextBacklog);
|
|
554
|
+
const entries = [
|
|
555
|
+
...arrayRecords(direct.entries),
|
|
556
|
+
...arrayRecords(nextBacklog.entries)
|
|
557
|
+
];
|
|
558
|
+
return entries.map((entry, index) => {
|
|
559
|
+
const id = textValue(entry.id ?? entry.taskId ?? entry.title, `backlog-${index + 1}`);
|
|
560
|
+
const status = normalized(entry.status ?? entry.state);
|
|
561
|
+
const column = ['todo', 'ready', 'queued', 'pending'].includes(status) || Boolean(entry.ready)
|
|
562
|
+
? 'todo'
|
|
563
|
+
: 'backlog';
|
|
564
|
+
return {
|
|
565
|
+
...entry,
|
|
566
|
+
boardKind: 'backlog',
|
|
567
|
+
boardColumn: column,
|
|
568
|
+
id: `backlog:${id}`,
|
|
569
|
+
taskId: id,
|
|
570
|
+
title: textValue(entry.title ?? entry.objective ?? entry.summary ?? id, id),
|
|
571
|
+
status: textValue(entry.status ?? entry.state, column === 'todo' ? 'todo' : 'backlog'),
|
|
572
|
+
lane: textValue(entry.lane ?? entry.group ?? entry.owner, 'backlog'),
|
|
573
|
+
changedPathCount: numberValue(entry.changedPathCount),
|
|
574
|
+
evidencePathCount: numberValue(entry.evidencePathCount)
|
|
575
|
+
};
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
function taskBoardColumns(items) {
|
|
579
|
+
const columns = [
|
|
580
|
+
{ id: 'backlog', title: 'Backlog', detail: 'not scheduled yet', empty: 'No backlog items.', items: [] },
|
|
581
|
+
{ id: 'todo', title: 'To do', detail: 'ready to start', empty: 'No queued tasks.', items: [] },
|
|
582
|
+
{ id: 'active', title: 'Active', detail: 'running work', empty: 'No active tasks.', items: [] },
|
|
583
|
+
{ id: 'review', title: 'Coordinator review', detail: 'needs decision', empty: 'No tasks need coordinator review.', items: [] },
|
|
584
|
+
{ id: 'ready', title: 'Ready', detail: 'ready to apply', empty: 'No ready patches.', items: [] },
|
|
585
|
+
{ id: 'done', title: 'Done', detail: 'completed output', empty: 'No completed tasks.', items: [] },
|
|
586
|
+
{ id: 'blocked', title: 'Blocked', detail: 'waiting or impossible', empty: 'No genuinely blocked tasks.', items: [] }
|
|
587
|
+
];
|
|
588
|
+
const byId = new Map(columns.map((column) => [column.id, column]));
|
|
589
|
+
for (const job of items)
|
|
590
|
+
byId.get(taskBoardColumnId(job))?.items.push(job);
|
|
591
|
+
for (const column of columns)
|
|
592
|
+
column.items.sort(taskBoardJobSort);
|
|
593
|
+
return columns;
|
|
594
|
+
}
|
|
595
|
+
const AGENT_DESCRIPTION_WORDS = [
|
|
596
|
+
'curious',
|
|
597
|
+
'steady',
|
|
598
|
+
'sharp',
|
|
599
|
+
'patient',
|
|
600
|
+
'bright',
|
|
601
|
+
'quiet',
|
|
602
|
+
'focused',
|
|
603
|
+
'careful',
|
|
604
|
+
'swift',
|
|
605
|
+
'calm',
|
|
606
|
+
'clear',
|
|
607
|
+
'brave',
|
|
608
|
+
'nimble',
|
|
609
|
+
'keen',
|
|
610
|
+
'warm',
|
|
611
|
+
'exact'
|
|
612
|
+
];
|
|
613
|
+
const AGENT_ANIMAL_NAMES = [
|
|
614
|
+
'fox',
|
|
615
|
+
'otter',
|
|
616
|
+
'hawk',
|
|
617
|
+
'lynx',
|
|
618
|
+
'raven',
|
|
619
|
+
'badger',
|
|
620
|
+
'heron',
|
|
621
|
+
'marten',
|
|
622
|
+
'falcon',
|
|
623
|
+
'orca',
|
|
624
|
+
'hare',
|
|
625
|
+
'wolf',
|
|
626
|
+
'owl',
|
|
627
|
+
'seal',
|
|
628
|
+
'ibis',
|
|
629
|
+
'koala'
|
|
630
|
+
];
|
|
631
|
+
const AGENT_COLORS = [
|
|
632
|
+
'#9fb7cf',
|
|
633
|
+
'#b7ad83',
|
|
634
|
+
'#9fbea8',
|
|
635
|
+
'#c3a0a0',
|
|
636
|
+
'#b2a6c8',
|
|
637
|
+
'#96b8b3',
|
|
638
|
+
'#c2b08f',
|
|
639
|
+
'#a9b1bb',
|
|
640
|
+
'#8fb29d',
|
|
641
|
+
'#baa0b4',
|
|
642
|
+
'#a5b8d0',
|
|
643
|
+
'#c0aa96'
|
|
644
|
+
];
|
|
645
|
+
function agentWorkers(jobs) {
|
|
646
|
+
const byAgent = new Map();
|
|
647
|
+
for (const job of jobs) {
|
|
648
|
+
const key = agentIdentityKey(job);
|
|
649
|
+
let worker = byAgent.get(key);
|
|
650
|
+
if (!worker) {
|
|
651
|
+
worker = {
|
|
652
|
+
key,
|
|
653
|
+
name: generatedAgentName(key),
|
|
654
|
+
color: agentColor(key),
|
|
655
|
+
status: 'idle',
|
|
656
|
+
jobs: [],
|
|
657
|
+
currentJobs: []
|
|
658
|
+
};
|
|
659
|
+
byAgent.set(key, worker);
|
|
660
|
+
}
|
|
661
|
+
worker.jobs.push(job);
|
|
662
|
+
if (isActiveAgentJob(job))
|
|
663
|
+
worker.currentJobs.push(job);
|
|
664
|
+
}
|
|
665
|
+
const workers = Array.from(byAgent.values());
|
|
666
|
+
for (const worker of workers) {
|
|
667
|
+
worker.jobs.sort(agentWorkerSort);
|
|
668
|
+
worker.currentJobs.sort(agentWorkerSort);
|
|
669
|
+
worker.status = coordinatorStatus(agentStatusCounts(worker.jobs));
|
|
670
|
+
if (!worker.currentJobs.length)
|
|
671
|
+
worker.currentJobs = worker.jobs.slice(0, 3);
|
|
672
|
+
}
|
|
673
|
+
return workers.sort((left, right) => agentStatusRank(left.status) - agentStatusRank(right.status) || left.name.localeCompare(right.name));
|
|
674
|
+
}
|
|
675
|
+
function agentIdentityKey(job) {
|
|
676
|
+
return textValue(job.agentId ?? job.workerId ?? job.worker ?? job.assignee ?? job.assignedAgent ?? job.computeId ?? job.taskId ?? job.id, taskCardId(job));
|
|
677
|
+
}
|
|
678
|
+
function generatedAgentName(key) {
|
|
679
|
+
const hash = stableNumber(key);
|
|
680
|
+
const description = AGENT_DESCRIPTION_WORDS[hash % AGENT_DESCRIPTION_WORDS.length] ?? 'curious';
|
|
681
|
+
const animal = AGENT_ANIMAL_NAMES[Math.floor(hash / AGENT_DESCRIPTION_WORDS.length) % AGENT_ANIMAL_NAMES.length] ?? 'fox';
|
|
682
|
+
return `${description} ${animal}`;
|
|
683
|
+
}
|
|
684
|
+
function agentDisplayName(worker, index) {
|
|
685
|
+
if (worker.name)
|
|
686
|
+
return worker.name;
|
|
687
|
+
return `Agent ${String(index + 1).padStart(2, '0')}`;
|
|
688
|
+
}
|
|
689
|
+
function agentColor(key) {
|
|
690
|
+
return AGENT_COLORS[stableNumber(key) % AGENT_COLORS.length] ?? AGENT_COLORS[0];
|
|
691
|
+
}
|
|
692
|
+
function stableNumber(value) {
|
|
693
|
+
let hash = 2_166_136_261;
|
|
694
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
695
|
+
hash ^= value.charCodeAt(index);
|
|
696
|
+
hash = Math.imul(hash, 16_777_619);
|
|
697
|
+
}
|
|
698
|
+
return hash >>> 0;
|
|
699
|
+
}
|
|
700
|
+
function agentStatusCounts(items) {
|
|
701
|
+
const counts = emptyAgentStatusCounts();
|
|
702
|
+
for (const item of items)
|
|
703
|
+
counts[agentStatus(item)] += 1;
|
|
704
|
+
return counts;
|
|
705
|
+
}
|
|
706
|
+
function emptyAgentStatusCounts() {
|
|
707
|
+
return { active: 0, waiting: 0, review: 0, done: 0, blocked: 0, idle: 0 };
|
|
708
|
+
}
|
|
709
|
+
function agentStatus(job) {
|
|
710
|
+
const status = normalized(job.status ?? job.state);
|
|
711
|
+
const bucket = normalized(job.bucket);
|
|
712
|
+
const disposition = normalized(job.disposition ?? job.mergeReadiness);
|
|
713
|
+
if (['running', 'active', 'working', 'in-progress', 'in progress'].includes(status))
|
|
714
|
+
return 'active';
|
|
715
|
+
if (status === 'idle' || bucket === 'idle')
|
|
716
|
+
return 'idle';
|
|
717
|
+
if (isBlockedJob(job))
|
|
718
|
+
return 'blocked';
|
|
719
|
+
if (isCoordinatorReviewJob(job) || isReadyJob(job) || disposition.includes('review'))
|
|
720
|
+
return 'review';
|
|
721
|
+
if (isCompletedJob(job) || ['done', 'landed', 'applied'].includes(bucket) || ['done', 'landed', 'applied'].includes(disposition))
|
|
722
|
+
return 'done';
|
|
723
|
+
if (['queued', 'pending', 'todo', 'waiting', 'scheduled'].includes(status) || ['queued', 'pending'].includes(bucket))
|
|
724
|
+
return 'waiting';
|
|
725
|
+
return 'waiting';
|
|
726
|
+
}
|
|
727
|
+
function isActiveAgentJob(job) {
|
|
728
|
+
return agentStatus(job) === 'active';
|
|
729
|
+
}
|
|
730
|
+
function coordinatorStatus(counts) {
|
|
731
|
+
if (counts.active)
|
|
732
|
+
return 'active';
|
|
733
|
+
if (counts.review)
|
|
734
|
+
return 'review';
|
|
735
|
+
if (counts.blocked)
|
|
736
|
+
return 'blocked';
|
|
737
|
+
if (counts.waiting)
|
|
738
|
+
return 'waiting';
|
|
739
|
+
if (counts.done)
|
|
740
|
+
return 'done';
|
|
741
|
+
return 'idle';
|
|
742
|
+
}
|
|
743
|
+
function agentStatusLabel(status) {
|
|
744
|
+
if (status === 'active')
|
|
745
|
+
return 'Active';
|
|
746
|
+
if (status === 'waiting')
|
|
747
|
+
return 'Waiting';
|
|
748
|
+
if (status === 'review')
|
|
749
|
+
return 'Review';
|
|
750
|
+
if (status === 'done')
|
|
751
|
+
return 'Done';
|
|
752
|
+
if (status === 'blocked')
|
|
753
|
+
return 'Blocked';
|
|
754
|
+
return 'Idle';
|
|
755
|
+
}
|
|
756
|
+
function agentModelLabel(job) {
|
|
757
|
+
const value = textValue(job.model ?? job.modelTier ?? job.profile ?? job.computeProfile ?? job.routingMode, '');
|
|
758
|
+
return value || 'model unknown';
|
|
759
|
+
}
|
|
760
|
+
function agentModelSummary(worker) {
|
|
761
|
+
const models = uniqueStrings(worker.jobs.map(agentModelLabel)).filter((model) => model !== 'model unknown');
|
|
762
|
+
if (!models.length)
|
|
763
|
+
return 'model unknown';
|
|
764
|
+
if (models.length === 1)
|
|
765
|
+
return models[0];
|
|
766
|
+
return `${models.slice(0, 2).join(' + ')}${models.length > 2 ? ` +${models.length - 2}` : ''}`;
|
|
767
|
+
}
|
|
768
|
+
function agentStatusDetail(job) {
|
|
769
|
+
const status = agentStatus(job);
|
|
770
|
+
if (status === 'active')
|
|
771
|
+
return 'Working on the assigned task now.';
|
|
772
|
+
if (status === 'waiting')
|
|
773
|
+
return 'Waiting to start, resume, or receive a lease.';
|
|
774
|
+
if (status === 'review')
|
|
775
|
+
return 'Output is waiting for coordinator review or application.';
|
|
776
|
+
if (status === 'blocked')
|
|
777
|
+
return taskReasonItems(job).slice(0, 2).join(', ') || 'Explicitly blocked by a dependency, missing decision, or impossible task.';
|
|
778
|
+
if (status === 'done')
|
|
779
|
+
return 'Worker output is complete.';
|
|
780
|
+
return 'Worker is idle.';
|
|
781
|
+
}
|
|
782
|
+
function agentTokenValue(worker) {
|
|
783
|
+
const actual = worker.jobs.reduce((sum, job) => sum + numberValue(job.actualInputTokens), 0);
|
|
784
|
+
const estimated = worker.jobs.reduce((sum, job) => sum + numberValue(job.estimatedInputTokens), 0);
|
|
785
|
+
const prompt = worker.jobs.reduce((sum, job) => sum + numberValue(job.promptBytes), 0);
|
|
786
|
+
if (actual)
|
|
787
|
+
return formatNumber(actual);
|
|
788
|
+
if (estimated)
|
|
789
|
+
return `${formatNumber(estimated)} est`;
|
|
790
|
+
if (prompt)
|
|
791
|
+
return formatBytes(prompt);
|
|
792
|
+
return '-';
|
|
793
|
+
}
|
|
794
|
+
function agentUncachedTokenValue(worker) {
|
|
795
|
+
const uncached = worker.jobs.reduce((sum, job) => sum + uncachedInputTokensForJob(job), 0);
|
|
796
|
+
return uncached ? formatNumber(uncached) : '-';
|
|
797
|
+
}
|
|
798
|
+
function agentRuntimeValue(worker, now) {
|
|
799
|
+
const durations = worker.currentJobs.map((job) => jobRuntimeMs(job, now)).filter((value) => value > 0);
|
|
800
|
+
if (!durations.length)
|
|
801
|
+
return '-';
|
|
802
|
+
return formatDuration(Math.max(...durations));
|
|
803
|
+
}
|
|
804
|
+
function jobRuntimeLabel(job, now) {
|
|
805
|
+
const duration = jobRuntimeMs(job, now);
|
|
806
|
+
return duration ? formatDuration(duration) : 'no timing';
|
|
807
|
+
}
|
|
808
|
+
function jobRuntimeMs(job, now) {
|
|
809
|
+
const explicit = numberValue(job.durationMs ?? job.elapsedMs ?? job.runtimeMs ?? job.wallTimeMs);
|
|
810
|
+
if (explicit > 0)
|
|
811
|
+
return explicit;
|
|
812
|
+
const started = timeValue(job.startedAt ?? job.startTime ?? job.createdAt ?? job.leaseStartedAt);
|
|
813
|
+
const ended = timeValue(job.finishedAt ?? job.completedAt ?? job.endedAt ?? job.updatedAt);
|
|
814
|
+
if (started && ended && ended >= started)
|
|
815
|
+
return ended - started;
|
|
816
|
+
if (started && isActiveAgentJob(job))
|
|
817
|
+
return Math.max(0, now - started);
|
|
818
|
+
return 0;
|
|
819
|
+
}
|
|
820
|
+
function agentTouchedFiles(worker) {
|
|
821
|
+
const paths = worker.jobs.flatMap((job) => stringArray(job.changedPaths));
|
|
822
|
+
if (paths.length)
|
|
823
|
+
return uniqueStrings(paths);
|
|
824
|
+
return worker.jobs
|
|
825
|
+
.map((job) => textValue(job.changedPath, ''))
|
|
826
|
+
.filter(Boolean);
|
|
827
|
+
}
|
|
828
|
+
function agentEvidencePaths(worker) {
|
|
829
|
+
return uniqueStrings(worker.jobs.flatMap((job) => stringArray(job.evidencePaths)));
|
|
830
|
+
}
|
|
831
|
+
function agentEvidenceCount(worker) {
|
|
832
|
+
const paths = agentEvidencePaths(worker);
|
|
833
|
+
if (paths.length)
|
|
834
|
+
return paths.length;
|
|
835
|
+
return worker.jobs.reduce((sum, job) => sum + numberValue(job.evidencePathCount), 0);
|
|
836
|
+
}
|
|
837
|
+
function agentWorkerSort(left, right) {
|
|
838
|
+
return agentStatusRank(agentStatus(left)) - agentStatusRank(agentStatus(right))
|
|
839
|
+
|| taskBoardJobSort(left, right);
|
|
840
|
+
}
|
|
841
|
+
function agentStatusRank(status) {
|
|
842
|
+
if (status === 'active')
|
|
843
|
+
return 0;
|
|
844
|
+
if (status === 'review')
|
|
845
|
+
return 1;
|
|
846
|
+
if (status === 'blocked')
|
|
847
|
+
return 2;
|
|
848
|
+
if (status === 'waiting')
|
|
849
|
+
return 3;
|
|
850
|
+
if (status === 'idle')
|
|
851
|
+
return 4;
|
|
852
|
+
return 5;
|
|
853
|
+
}
|
|
854
|
+
function taskBoardColumnId(job) {
|
|
855
|
+
if (job.boardColumn)
|
|
856
|
+
return job.boardColumn;
|
|
857
|
+
if (job.boardKind === 'backlog')
|
|
858
|
+
return 'backlog';
|
|
859
|
+
if (isBlockedJob(job))
|
|
860
|
+
return 'blocked';
|
|
861
|
+
if (isCoordinatorReviewJob(job))
|
|
862
|
+
return 'review';
|
|
863
|
+
if (isReadyJob(job))
|
|
864
|
+
return 'ready';
|
|
865
|
+
if (['queued', 'pending', 'todo'].includes(normalized(job.status)))
|
|
866
|
+
return 'todo';
|
|
867
|
+
if (!isTerminalJob(job) || normalized(job.status) === 'running')
|
|
868
|
+
return 'active';
|
|
869
|
+
return 'done';
|
|
870
|
+
}
|
|
871
|
+
function taskBoardColumnTitle(id) {
|
|
872
|
+
if (id === 'backlog')
|
|
873
|
+
return 'Backlog';
|
|
874
|
+
if (id === 'todo')
|
|
875
|
+
return 'To do';
|
|
876
|
+
if (id === 'active')
|
|
877
|
+
return 'Active';
|
|
878
|
+
if (id === 'review')
|
|
879
|
+
return 'Coordinator review';
|
|
880
|
+
if (id === 'ready')
|
|
881
|
+
return 'Ready';
|
|
882
|
+
if (id === 'done')
|
|
883
|
+
return 'Done';
|
|
884
|
+
return 'Blocked';
|
|
885
|
+
}
|
|
886
|
+
function taskBoardJobSort(left, right) {
|
|
887
|
+
return taskSortRank(left) - taskSortRank(right)
|
|
888
|
+
|| taskTitle(left).localeCompare(taskTitle(right));
|
|
889
|
+
}
|
|
890
|
+
function taskSortRank(job) {
|
|
891
|
+
if (job.boardKind === 'backlog')
|
|
892
|
+
return 0;
|
|
893
|
+
if (isBlockedJob(job))
|
|
894
|
+
return 0;
|
|
895
|
+
if (isNeedsCoordinatorPortJob(job))
|
|
896
|
+
return 1;
|
|
897
|
+
if (isFailedJob(job) || isStaleJob(job))
|
|
898
|
+
return 2;
|
|
899
|
+
if (normalized(job.status) === 'running')
|
|
900
|
+
return 3;
|
|
901
|
+
if (isReadyJob(job))
|
|
902
|
+
return 4;
|
|
903
|
+
return 5;
|
|
904
|
+
}
|
|
905
|
+
function taskCardId(job) {
|
|
906
|
+
return textValue(job.id ?? job.taskId ?? job.title, 'task');
|
|
907
|
+
}
|
|
908
|
+
function ticketId(job) {
|
|
909
|
+
return `T-${stableHash(taskCardId(job)).slice(0, 5).toUpperCase()}`;
|
|
910
|
+
}
|
|
911
|
+
function stableHash(value) {
|
|
912
|
+
let hash = 2_166_136_261;
|
|
913
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
914
|
+
hash ^= value.charCodeAt(index);
|
|
915
|
+
hash = Math.imul(hash, 16_777_619);
|
|
916
|
+
}
|
|
917
|
+
return (hash >>> 0).toString(36).padStart(6, '0');
|
|
918
|
+
}
|
|
919
|
+
function taskTitle(job) {
|
|
920
|
+
return sentenceCaseIdentifier(textValue(job.title ?? job.taskId ?? job.id, 'Untitled task'));
|
|
921
|
+
}
|
|
922
|
+
function taskCardStatus(job) {
|
|
923
|
+
const decision = coordinatorDecisionStatus(job);
|
|
924
|
+
if (decision)
|
|
925
|
+
return coordinatorDecisionLabel(decision);
|
|
926
|
+
const column = taskBoardColumnId(job);
|
|
927
|
+
if (column === 'backlog')
|
|
928
|
+
return 'Backlog';
|
|
929
|
+
if (column === 'todo')
|
|
930
|
+
return 'To do';
|
|
931
|
+
if (column === 'review')
|
|
932
|
+
return 'Coordinator review';
|
|
933
|
+
if (column === 'ready')
|
|
934
|
+
return 'Ready';
|
|
935
|
+
if (column === 'blocked')
|
|
936
|
+
return 'Blocked';
|
|
937
|
+
if (column === 'done')
|
|
938
|
+
return 'Done';
|
|
939
|
+
return textValue(job.status, 'Active');
|
|
940
|
+
}
|
|
941
|
+
function taskStatusDetail(job) {
|
|
942
|
+
const decision = recordValue(job.coordinatorDecision);
|
|
943
|
+
const reason = textValue(decision.reason, '');
|
|
944
|
+
if (reason)
|
|
945
|
+
return reason;
|
|
946
|
+
return coordinatorFacingSignalLabel(job.bucket ?? job.disposition ?? job.mergeReadiness ?? job.status);
|
|
947
|
+
}
|
|
948
|
+
function taskDetailSummary(job) {
|
|
949
|
+
const decision = recordValue(job.coordinatorDecision);
|
|
950
|
+
const reason = textValue(decision.reason, '');
|
|
951
|
+
if (reason)
|
|
952
|
+
return `Coordinator decision: ${coordinatorDecisionLabel(textValue(job.coordinatorDecisionStatus ?? decision.status, 'resolved'))}. ${reason}`;
|
|
953
|
+
const column = taskBoardColumnId(job);
|
|
954
|
+
if (column === 'backlog')
|
|
955
|
+
return 'This is planned work that is not scheduled into the active swarm yet.';
|
|
956
|
+
if (column === 'todo')
|
|
957
|
+
return 'This work is queued for the AI-managed task backlog.';
|
|
958
|
+
if (column === 'review')
|
|
959
|
+
return 'The worker produced output that the coordinator should review before it is treated as landed work.';
|
|
960
|
+
if (column === 'ready')
|
|
961
|
+
return 'This task appears ready to apply or already has a clean readiness signal.';
|
|
962
|
+
if (column === 'blocked')
|
|
963
|
+
return 'This task is explicitly blocked by a dependency, missing decision, or an impossible requirement.';
|
|
964
|
+
if (column === 'done')
|
|
965
|
+
return 'This task has completed and is being kept visible as successful swarm output.';
|
|
966
|
+
return 'This task is still active or queued in the swarm run.';
|
|
967
|
+
}
|
|
968
|
+
function taskMarkdownSummary(job) {
|
|
969
|
+
const changed = formatNumber(numberValue(job.changedPathCount));
|
|
970
|
+
const evidence = formatNumber(numberValue(job.evidencePathCount));
|
|
971
|
+
const reasons = taskReasonItems(job);
|
|
972
|
+
const reasonText = reasons.length ? ` Main signal: ${reasons[0]}.` : '';
|
|
973
|
+
return `${taskTitle(job)} is in ${taskBoardColumnTitle(taskBoardColumnId(job)).toLowerCase()} with ${changed} changed paths and ${evidence} evidence artifacts.${reasonText}`;
|
|
974
|
+
}
|
|
975
|
+
function coordinatorDecisionStatus(job) {
|
|
976
|
+
return textValue(job.coordinatorDecisionStatus ?? recordValue(job.coordinatorDecision).status, '');
|
|
977
|
+
}
|
|
978
|
+
function coordinatorDecisionLabel(status) {
|
|
979
|
+
const value = normalized(status);
|
|
980
|
+
if (value.includes('superseded'))
|
|
981
|
+
return 'Superseded';
|
|
982
|
+
if (value.includes('not-applicable'))
|
|
983
|
+
return 'Not applicable';
|
|
984
|
+
if (value.includes('accepted') || value.includes('applied'))
|
|
985
|
+
return 'Accepted';
|
|
986
|
+
if (value.includes('rejected'))
|
|
987
|
+
return 'Rejected';
|
|
988
|
+
return sentenceCaseIdentifier(status || 'resolved');
|
|
989
|
+
}
|
|
990
|
+
function coordinatorFacingSignalLabel(value) {
|
|
991
|
+
const raw = textValue(value, 'tracked task');
|
|
992
|
+
const normalizedValue = normalized(raw);
|
|
993
|
+
if (normalizedValue === 'needs-human-port' || normalizedValue === 'needs-coordinator-port')
|
|
994
|
+
return 'needs coordinator review';
|
|
995
|
+
if (normalizedValue === 'needs-human-review' || normalizedValue === 'needs-coordinator-review')
|
|
996
|
+
return 'needs coordinator review';
|
|
997
|
+
if (normalizedValue === 'needs-human-decision' || normalizedValue === 'needs-coordinator-decision')
|
|
998
|
+
return 'needs coordinator decision';
|
|
999
|
+
if (normalizedValue === 'failed-evidence')
|
|
1000
|
+
return 'failed evidence';
|
|
1001
|
+
if (normalizedValue === 'ready-to-apply')
|
|
1002
|
+
return 'ready to apply';
|
|
1003
|
+
if (normalizedValue === 'patch-candidate')
|
|
1004
|
+
return 'patch candidate';
|
|
1005
|
+
return sentenceCaseIdentifier(raw);
|
|
1006
|
+
}
|
|
1007
|
+
function taskTokenSummary(job) {
|
|
1008
|
+
const actual = numberValue(job.actualInputTokens);
|
|
1009
|
+
const uncached = numberValue(job.uncachedInputTokens);
|
|
1010
|
+
const estimated = numberValue(job.estimatedInputTokens);
|
|
1011
|
+
if (actual)
|
|
1012
|
+
return `${formatNumber(actual)} input`;
|
|
1013
|
+
if (uncached)
|
|
1014
|
+
return `${formatNumber(uncached)} uncached`;
|
|
1015
|
+
if (estimated)
|
|
1016
|
+
return `${formatNumber(estimated)} est`;
|
|
1017
|
+
return '-';
|
|
1018
|
+
}
|
|
1019
|
+
function taskReasonItems(job) {
|
|
1020
|
+
return uniqueStrings([
|
|
1021
|
+
...stringArray(job.collectReasonClasses),
|
|
1022
|
+
...stringArray(job.reasons),
|
|
1023
|
+
...stringArray(job.contextBudgetWarnings),
|
|
1024
|
+
...stringArray(job.semanticReadinessReasons)
|
|
1025
|
+
]).slice(0, 8);
|
|
1026
|
+
}
|
|
1027
|
+
function taskFact(label, value) {
|
|
1028
|
+
return _jsxs("div", { className: "task-fact", children: [_jsx("span", { children: label }), _jsx("b", { children: value || '-' })] });
|
|
1029
|
+
}
|
|
1030
|
+
function workKpi(label, value, detail) {
|
|
1031
|
+
return _jsxs("article", { className: "work-kpi", children: [_jsx("span", { children: label }), _jsx("b", { children: text(value) }), _jsx("small", { children: detail })] });
|
|
1032
|
+
}
|
|
1033
|
+
function HumanActionQueue({ dashboard, jobs, audit }) {
|
|
1034
|
+
const rows = humanActionRows(jobs, audit, dashboard);
|
|
1035
|
+
return _jsx("div", { className: "action-layout", "data-scroll-id": "actions", children: _jsx(ActionList, { rows: rows, expanded: true }) });
|
|
1036
|
+
}
|
|
1037
|
+
function ActionList({ rows, expanded = false }) {
|
|
1038
|
+
if (!rows.length)
|
|
1039
|
+
return _jsx("p", { className: "empty tight", children: "No open questions from agents right now." });
|
|
1040
|
+
return _jsx("div", { className: expanded ? 'action-list expanded' : 'action-list', children: rows.map((row) => _jsxs("article", { className: `action-row ${row.priority}`, "data-human-question-code": row.code, children: [_jsxs("div", { className: "action-code", children: [_jsx("b", { children: row.code }), _jsx("button", { type: "button", "data-copy-code": row.code, title: `Copy ${row.code}`, children: "Copy" })] }), _jsxs("div", { className: "action-copy", children: [_jsxs("span", { children: [row.askedBy || 'Agent question', row.scope ? ` · ${row.scope}` : ''] }), _jsx("h4", { children: row.title }), _jsx("p", { children: row.question }), expanded && row.why ? _jsxs("small", { children: ["Why: ", row.why] }) : null, expanded && row.detail !== row.question ? _jsxs("small", { children: ["Context: ", row.detail] }) : null, expanded ? _jsxs("small", { children: ["Requested answer: ", row.requestedAnswer || row.defaultAction] }) : null, expanded ? _jsx(HumanActionAnswerForm, { row: row }) : null] })] })) });
|
|
1041
|
+
}
|
|
1042
|
+
function HumanActionAnswerForm({ row }) {
|
|
1043
|
+
const state = humanAnswerStates.get(row.code);
|
|
1044
|
+
const answer = humanAnswerDrafts.get(row.code) ?? '';
|
|
1045
|
+
return _jsxs("form", { className: "action-answer-form", "data-human-answer-form": "true", "data-human-answer-code": row.code, children: [_jsx("textarea", { name: "answer", "data-human-answer-code": row.code, rows: 3, placeholder: `Answer ${row.code}...`, "aria-label": `Answer ${row.code}`, children: answer }), _jsxs("div", { children: [_jsx("button", { type: "submit", disabled: state?.status === 'submitting', children: state?.status === 'submitting' ? 'Submitting...' : 'Submit answer' }), state ? _jsx("small", { className: `action-answer-status ${state.status}`, children: state.message }) : null] })] });
|
|
1046
|
+
}
|
|
1047
|
+
function PerformanceView({ dashboard, jobs, attention, audit }) {
|
|
1048
|
+
const telemetry = tokenTimeSummary(dashboard, jobs);
|
|
1049
|
+
const context = contextPressureSummary(jobs);
|
|
1050
|
+
const cost = modelCostSummary(jobs);
|
|
1051
|
+
const timeCharts = performanceTimeChartSeries(dashboard, jobs);
|
|
1052
|
+
const optimization = optimizationSignalSummary(dashboard, jobs);
|
|
1053
|
+
const behavior = optimizationBehaviorSummary(dashboard, jobs);
|
|
1054
|
+
const topContext = contextOffenderRows(jobs);
|
|
1055
|
+
const topCosts = modelCostRows(jobs);
|
|
1056
|
+
return _jsxs("div", { className: "performance-layout", "data-scroll-id": "performance", "data-smoke-marker": "performance-tab", children: [_jsxs("section", { className: "performance-summary", children: [workKpi('Estimated cost', cost.value, cost.detail), workKpi('Input tokens', telemetry.tokenValue, telemetry.tokenDetail), workKpi('Runtime', telemetry.durationValue, telemetry.durationDetail), workKpi('Cache hit', context.cacheHitRatio ? formatPercent(context.cacheHitRatio) : '-', context.actualInputTokens ? `${formatNumber(context.cachedInputTokens)} cached` : 'no cache data'), workKpi('Waste signals', context.warningCount + context.failedCount + attention.failedCount + attention.blockedCount + generatedNoiseCount(audit), 'budget, failure, blocked, and generated-noise signals')] }), _jsxs("section", { className: "work-section performance-chart-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Performance over time" }), _jsx("span", { children: performanceTimeDetail(dashboard) })] }), _jsx(MiniCharts, { series: timeCharts })] }), _jsxs("section", { className: "work-section performance-chart-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Optimization signals" }), _jsx("span", { children: optimization.status })] }), _jsx("div", { className: "optimization-signal-grid", children: optimization.rows.map((row) => _jsxs("article", { className: `optimization-signal ${row.tone}`, children: [_jsx("span", { children: row.label }), _jsx("b", { children: row.value }), _jsx("small", { children: row.detail })] })) })] }), _jsxs("section", { className: "work-section optimization-behavior-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Workflow behavior trend" }), _jsx("span", { children: behavior.status })] }), _jsx("div", { className: "optimization-behavior-grid", children: behavior.cards.map((card) => _jsxs("article", { className: `optimization-behavior-card ${card.tone}`, children: [_jsx("span", { children: card.label }), _jsx("b", { children: card.value }), _jsx("small", { children: card.detail })] })) }), _jsx("div", { className: "optimization-behavior-list", children: behavior.rows.map((row) => _jsxs("article", { className: `optimization-behavior-row ${row.tone}`, children: [_jsxs("div", { children: [_jsx("b", { children: row.label }), _jsx("small", { children: row.detail })] }), _jsx("span", { children: row.value })] })) })] }), _jsxs("section", { className: "work-section performance-split", children: [_jsxs("div", { children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Top cost drivers" }), _jsxs("span", { children: [text(Math.max(topCosts.length, topContext.length)), " tasks"] })] }), _jsx(SimpleRows, { rows: topCosts.length ? topCosts : topContext.map((job) => ({
|
|
1057
|
+
label: job.label,
|
|
1058
|
+
value: formatNumber(job.uncachedInputTokens || job.actualInputTokens || job.estimatedInputTokens),
|
|
1059
|
+
detail: contextDriverDetail(job)
|
|
1060
|
+
})) })] }), _jsxs("div", { children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Waste breakdown" }), _jsx("span", { children: riskStatusLabel(dashboard, attention, jobs.length > 0) })] }), _jsx(BarRows, { rows: performanceWasteRows(jobs, attention, audit, context) })] })] })] });
|
|
1061
|
+
}
|
|
1062
|
+
function HistoryView({ dashboard, jobs, events, success, attention }) {
|
|
1063
|
+
const graph = historyGitGraph(dashboard, jobs);
|
|
1064
|
+
return _jsxs("div", { className: "history-layout", "data-scroll-id": "history", "data-smoke-marker": "history-tab", children: [_jsxs("section", { className: "work-section history-graph-section git-history-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Version history" }), _jsxs("span", { children: [text(graph.rows.length), " nodes \u00B7 ", text(graph.lanes.length), " lanes"] })] }), _jsx(HistoryGitGraph, { graph: graph })] }), _jsxs("section", { className: "work-section history-split", children: [_jsxs("div", { children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Merge state" }), _jsxs("span", { children: [formatNumber(success.appliedCount), " applied \u00B7 ", formatNumber(success.landedCount), " landed"] })] }), _jsx(SimpleRows, { rows: [
|
|
1065
|
+
{ label: 'Ready', value: formatNumber(success.readyCount), detail: 'outputs that appear ready to apply' },
|
|
1066
|
+
{ label: 'Coordinator review', value: formatNumber(attention.needsCoordinatorReviewCount), detail: 'outputs waiting on coordinator judgement' },
|
|
1067
|
+
{ label: 'Review / blocked', value: formatNumber(attention.failedCount + attention.needsCoordinatorReviewCount + attention.staleCount + attention.blockedCount), detail: 'outputs needing coordinator decision or explicit unblock' },
|
|
1068
|
+
{ label: 'Snapshot', value: formatTime(dashboard.generatedAt), detail: dashboardSourceLabel(dashboard) }
|
|
1069
|
+
] })] }), _jsxs("div", { children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Recent events" }), _jsxs("span", { children: [text(events.length), " visible"] })] }), _jsx(HistoryEventRows, { events: events })] })] })] });
|
|
1070
|
+
}
|
|
1071
|
+
function HistoryGitGraph({ graph }) {
|
|
1072
|
+
if (!graph.rows.length)
|
|
1073
|
+
return _jsx("p", { className: "empty tight", children: "No task history has been reported yet." });
|
|
1074
|
+
return _jsx("div", { className: "git-history-viewport", children: _jsxs("div", { className: "git-history-grid", style: `--history-row-height:${graph.rowHeight}px; --history-graph-width:${graph.width}px; --history-graph-height:${graph.height}px`, children: [_jsx("div", { className: "git-graph-canvas", "aria-hidden": "true", children: _jsxs("svg", { className: "git-graph-svg", viewBox: `0 0 ${graph.width} ${graph.height}`, width: graph.width, height: graph.height, children: [graph.lanes.map((lane) => _jsx("path", { className: "git-graph-rail", d: `M ${lane.x} 14 V ${Math.max(14, graph.height - 14)}`, stroke: lane.color })), graph.rows.filter((row) => row.laneIndex > 0).map((row) => _jsx("path", { className: row.merged ? 'git-graph-merge' : 'git-graph-branch', d: historyCurvePath(graph.trunkX, row.x, row.y), stroke: historyLaneColor(graph, row.laneId) })), graph.rows.filter((row) => row.merged && row.laneIndex > 0).map((row) => _jsx("circle", { className: `git-graph-node trunk ${row.tone}`, cx: graph.trunkX, cy: row.y, r: "4", stroke: historyLaneColor(graph, 'main'), "data-chart-tooltip": `${row.title}\nMerged to main\n${row.meta}` })), graph.rows.map((row) => _jsx("circle", { className: `git-graph-node ${row.tone}`, cx: row.x, cy: row.y, r: "5", stroke: historyLaneColor(graph, row.laneId), "data-chart-tooltip": row.tooltip }))] }) }), _jsx("div", { className: "git-history-rows", children: graph.rows.map((row) => _jsxs("button", { type: "button", className: `git-history-row ${row.tone}`, "data-task-card": row.id, "data-chart-tooltip": row.tooltip, "aria-label": row.tooltip.replace(/\n/g, '. '), style: `height:${graph.rowHeight}px`, children: [_jsxs("div", { children: [_jsx("b", { children: row.title }), _jsx("small", { children: row.subtitle })] }), _jsx("span", { children: row.meta })] })) })] }) });
|
|
1075
|
+
}
|
|
1076
|
+
function LegacyHistoryBranchList({ jobs }) {
|
|
1077
|
+
const branches = historyBranchRows(jobs);
|
|
1078
|
+
return _jsxs("section", { className: "work-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Version lanes" }), _jsxs("span", { children: [text(branches.length), " recent task outputs"] })] }), _jsx("div", { className: "history-branches", children: branches.length ? branches.map((job) => _jsxs("article", { className: `history-branch ${jobRisk(job)}`, children: [_jsx("span", { className: "history-branch-rail", "aria-hidden": "true" }), _jsxs("div", { children: [_jsxs("b", { children: [ticketId(job), " \u00B7 ", taskTitle(job)] }), _jsxs("small", { children: [laneOf(job), " \u00B7 ", taskCardStatus(job), " \u00B7 ", pathSummaryText(job)] })] }), _jsx("span", { children: jobRuntimeLabel(job, Date.now()) })] })) : _jsx("p", { className: "empty tight", children: "No task outputs are available for this history view yet." }) })] });
|
|
1079
|
+
}
|
|
1080
|
+
function TestingView({ jobs, events }) {
|
|
1081
|
+
const summary = testingSummary(jobs, events);
|
|
1082
|
+
return _jsxs("div", { className: "testing-layout", "data-scroll-id": "testing", "data-smoke-marker": "testing-tab", children: [_jsxs("section", { className: "testing-summary", children: [workKpi('Checks passing', summary.totalChecks ? `${formatNumber(summary.passedChecks)}/${formatNumber(summary.totalChecks)}` : '-', summary.totalChecks ? `${formatNumber(summary.failedChecks)} failing` : 'no command metadata'), workKpi('Active work', summary.activeJobs ? formatNumber(summary.activeJobs) : '0', summary.activeJobs ? 'verification still pending' : 'no running tasks'), workKpi('Browser evidence', summary.browserEvidence ? formatNumber(summary.browserEvidence) : '-', 'Playwright, screenshot, DOM, or browser artifacts'), workKpi('Fuzzing', summary.fuzzEvidence ? formatNumber(summary.fuzzEvidence) : '-', 'fuzz or property-test evidence paths'), workKpi('Oracles', summary.oracleEvidence ? formatNumber(summary.oracleEvidence) : '-', 'oracle, golden, fixture, or snapshot evidence')] }), summary.notice ? _jsx("p", { className: `testing-notice ${summary.noticeTone}`, children: summary.notice }) : null, _jsxs("section", { className: "work-section testing-split", children: [_jsxs("div", { children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Quality status" }), _jsx("span", { children: summary.status })] }), _jsx(BarRows, { rows: [
|
|
1083
|
+
{ label: 'Passed checks', value: summary.passedChecks, detail: formatNumber(summary.passedChecks), tone: summary.passedChecks ? 'good' : 'neutral' },
|
|
1084
|
+
{ label: 'Failed checks', value: summary.failedChecks, detail: formatNumber(summary.failedChecks), tone: summary.failedChecks ? 'bad' : 'neutral' },
|
|
1085
|
+
{ label: 'Active work', value: summary.activeJobs, detail: `${formatNumber(summary.activeJobs)} tasks`, tone: summary.activeJobs ? 'warn' : 'neutral' },
|
|
1086
|
+
{ label: 'Missing metadata', value: summary.noMetadataTasks, detail: `${formatNumber(summary.noMetadataTasks)} tasks`, tone: summary.noMetadataTasks ? 'warn' : 'neutral' },
|
|
1087
|
+
{ label: 'Evidence-backed tasks', value: summary.evidenceTasks, detail: `${formatNumber(summary.evidenceTasks)} tasks`, tone: 'neutral' },
|
|
1088
|
+
{ label: 'Open failures', value: summary.openFailures, detail: `${formatNumber(summary.openFailures)} tasks`, tone: summary.openFailures ? 'bad' : 'neutral' }
|
|
1089
|
+
] })] }), _jsxs("div", { children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Evidence mix" }), _jsxs("span", { children: [formatNumber(summary.evidencePaths), " paths"] })] }), _jsxs("div", { className: "testing-evidence-grid", children: [testingEvidenceCard('Browser', summary.browserEvidence, 'UI, screenshot, DOM, or Playwright'), testingEvidenceCard('Fuzzing', summary.fuzzEvidence, 'fuzz/property runs'), testingEvidenceCard('Oracle', summary.oracleEvidence, 'golden/reference checks'), testingEvidenceCard('Unit / smoke', summary.unitEvidence, 'test, smoke, or spec output')] })] })] }), _jsxs("section", { className: "work-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Recent check output" }), _jsxs("span", { children: [formatNumber(summary.rows.length), " tasks"] })] }), _jsx("div", { className: "testing-rows", children: summary.rows.length ? summary.rows.map((row) => _jsxs("article", { className: `testing-row ${row.tone}`, children: [_jsxs("div", { className: "testing-row-copy", children: [_jsx("b", { children: row.label }), _jsx("div", { className: "testing-row-meta", children: row.detailParts.map((part) => _jsx("small", { children: part })) })] }), _jsx("span", { children: row.value })] })) : _jsx("p", { className: "empty tight", children: "No check metadata has been reported yet. This is not the same as passing tests." }) })] })] });
|
|
1090
|
+
}
|
|
1091
|
+
function Metrics({ dashboard, lanes, jobs, events, attention, audit }) {
|
|
1092
|
+
const telemetry = tokenTimeSummary(dashboard, jobs);
|
|
1093
|
+
const context = contextPressureSummary(jobs);
|
|
1094
|
+
const topContext = contextOffenderRows(jobs);
|
|
1095
|
+
const usefulOutput = jobs.length ? successLikeJobCount(jobs) / jobs.length : 0;
|
|
1096
|
+
void lanes;
|
|
1097
|
+
return _jsxs("div", { className: "efficiency-layout", "data-scroll-id": "efficiency", children: [_jsxs("section", { className: "efficiency-summary", children: [workKpi('Useful output rate', formatPercent(usefulOutput), `${text(successLikeJobCount(jobs))} useful of ${text(jobs.length)}`), workKpi('Uncached tokens', formatNumber(context.uncachedInputTokens), `${telemetry.tokenValue} total input`), workKpi('Budget warnings', context.warningCount + context.failedCount, `${text(context.failedCount)} failed budget`), workKpi('Runtime', telemetry.durationValue, telemetry.durationDetail), workKpi('Events', events.length, 'visible event rows')] }), _jsxs("section", { className: "work-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Top cost drivers" }), _jsxs("span", { children: [text(topContext.length), " tasks"] })] }), _jsx(SimpleRows, { rows: topContext.map((job) => ({
|
|
1098
|
+
label: job.label,
|
|
1099
|
+
value: formatNumber(job.uncachedInputTokens || job.actualInputTokens || job.estimatedInputTokens),
|
|
1100
|
+
detail: contextDriverDetail(job)
|
|
1101
|
+
})) })] }), _jsxs("section", { className: "work-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Performance concerns" }), _jsx("span", { children: riskStatusLabel(dashboard, attention, jobs.length > 0) })] }), _jsx(SimpleRows, { rows: [
|
|
1102
|
+
{ label: 'Coordinator review', value: text(attention.failedCount + attention.needsCoordinatorReviewCount + attention.staleCount), detail: 'failed, stale, or coordinator-review outputs' },
|
|
1103
|
+
{ label: 'Blocked', value: text(attention.blockedCount), detail: 'explicit dependency or impossible-task blocks' },
|
|
1104
|
+
{ label: 'Source ownership risk', value: text(audit.sourceOwnershipViolationCount), detail: 'outside declared source ownership' },
|
|
1105
|
+
{ label: 'Generated noise', value: text(generatedNoiseCount(audit)), detail: 'ignored generated/cache output' },
|
|
1106
|
+
{ label: 'Context warning jobs', value: text(context.warningCount), detail: 'higher token/cost risk' }
|
|
1107
|
+
] })] })] });
|
|
1108
|
+
}
|
|
1109
|
+
function MergeView({ dashboard, jobs, success, attention }) {
|
|
1110
|
+
const semantic = semanticMetrics(dashboard.semantic);
|
|
1111
|
+
const stages = [
|
|
1112
|
+
{ id: 'workers', label: 'Worker outputs', value: jobs.length, tone: 'neutral' },
|
|
1113
|
+
{ id: 'review', label: 'Coordinator review', value: attention.needsCoordinatorReviewCount, tone: attention.needsCoordinatorReviewCount ? 'warn' : 'neutral' },
|
|
1114
|
+
{ id: 'ready', label: 'Ready', value: success.readyCount, tone: success.readyCount ? 'good' : 'neutral' },
|
|
1115
|
+
{ id: 'applied', label: 'Applied', value: success.appliedCount, tone: success.appliedCount ? 'good' : 'neutral' },
|
|
1116
|
+
{ id: 'blocked', label: 'Blocked', value: attention.failedCount + attention.staleCount, tone: attention.failedCount + attention.staleCount ? 'bad' : 'neutral' }
|
|
1117
|
+
];
|
|
1118
|
+
return _jsxs("div", { className: "merge-layout", "data-scroll-id": "merge", children: [_jsx("section", { className: "merge-flow", "aria-label": "Semantic merge and patch flow", children: stages.map((stage) => _jsxs("article", { className: `merge-node ${stage.tone}`, children: [_jsx("span", { children: stage.label }), _jsx("b", { children: text(stage.value) })] })) }), _jsxs("section", { className: "work-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Semantic merge status" }), _jsxs("span", { children: [formatNumber(semantic.total), " signals"] })] }), _jsx(SimpleRows, { rows: [
|
|
1119
|
+
{ label: 'Auto-merge candidates', value: formatNumber(semantic.autoMerge), detail: 'semantic scripts that may be portable' },
|
|
1120
|
+
{ label: 'Replay accepted clean', value: formatNumber(semantic.acceptedClean), detail: 'clean semantic replay outcomes' },
|
|
1121
|
+
{ label: 'Replay conflicts', value: formatNumber(semantic.conflicts), detail: 'must be reviewed before merge' },
|
|
1122
|
+
{ label: 'Expected imports satisfied', value: `${formatNumber(semantic.satisfied)}/${formatNumber(semantic.expected)}`, detail: 'semantic import coverage' }
|
|
1123
|
+
] })] }), _jsxs("section", { className: "work-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Patch/version tree" }), _jsxs("span", { children: [text(jobs.length), " worker leaves"] })] }), _jsx("div", { className: "version-tree", children: jobs.slice(0, 18).map((job) => _jsxs("article", { className: `version-leaf ${jobRisk(job)}`, children: [_jsx("b", { children: text(job.id) }), _jsxs("small", { children: [text(job.disposition ?? job.mergeReadiness ?? job.status), " \u00B7 ", pathSummaryText(job)] })] })) })] })] });
|
|
1124
|
+
}
|
|
1125
|
+
function EvidenceView({ jobs, events, sourceEntries, dashboard }) {
|
|
1126
|
+
return _jsxs("div", { className: "evidence-layout", "data-scroll-id": "evidence", children: [_jsxs("section", { className: "work-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Jobs" }), _jsxs("span", { children: [text(jobs.length), " visible"] })] }), _jsx(Jobs, { jobs: jobs })] }), _jsxs("section", { className: "work-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Events" }), _jsxs("span", { children: [text(events.length), " visible"] })] }), _jsx(Events, { events: events })] }), _jsxs("section", { className: "work-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Sources" }), _jsxs("span", { children: [text(sourceEntries.length), " paths"] })] }), _jsx(Sources, { entries: sourceEntries })] }), _jsxs("section", { className: "work-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Run context" }), _jsx("span", { children: "readonly" })] }), _jsx(RunContext, { routing: dashboard.routing, backlog: dashboard.backlog })] })] });
|
|
1127
|
+
}
|
|
1128
|
+
function Overview({ dashboard, lanes, jobs, events, attention, audit, success }) {
|
|
1129
|
+
const health = dashboardHealthSummary(dashboard, jobs, attention);
|
|
1130
|
+
const telemetry = tokenTimeSummary(dashboard, jobs);
|
|
1131
|
+
const pressure = warningPressureSummary(attention, audit, health, telemetry);
|
|
1132
|
+
const progressCharts = progressChartSeries(dashboard, jobs, events).slice(0, 3);
|
|
1133
|
+
return _jsxs("section", { className: "overview-grid", children: [_jsxs("section", { className: "overview-snapshot", "data-smoke-marker": "landed-health-summary", "aria-label": "Landed success, health, warning pressure, token, and time snapshot", children: [snapshotTile('Run health', health.status, `${formatPercent(health.completionRatio)} complete · ${text(health.failedJobCount + health.blockedJobCount)} failed/blocked`, health.tone), snapshotTile('Applied / landed', `${formatNumber(success.appliedCount)}/${formatNumber(success.applyTotalCount || success.appliedCount || success.landedCount)}`, `${formatNumber(success.landedCount)} landed · ${formatNumber(success.applyFailedCount)} ledger failed`, success.applyFailedCount ? 'bad' : success.appliedCount || success.landedCount ? 'good' : 'neutral'), snapshotTile('Warning pressure', pressure.headline, pressure.detail, pressure.tone), snapshotTile('Token load', telemetry.tokenValue, telemetry.tokenDetail, telemetry.tokenTone), snapshotTile('Run time', telemetry.durationValue, telemetry.durationDetail, telemetry.timeTone)] }), _jsx(Panel, { title: "Run health", meta: `${text(jobs.length)} visible jobs`, children: _jsxs("div", { className: "overview-card-scroll", children: [_jsx(RunHealthSummary, { health: health }), _jsxs("div", { className: "overview-metrics", children: [compactMetric('Healthy', health.healthyJobCount), compactMetric('Warning', health.warningJobCount), compactMetric('Failed', health.failedJobCount), compactMetric('Terminal', health.terminalJobCount), compactMetric('Ready', health.readyToApplyJobCount), compactMetric('Semantic clean', health.semanticCleanJobCount)] }), _jsx(MiniCharts, { series: progressCharts, compact: true })] }) }), _jsx(Panel, { title: "Warning pressure", meta: `${formatNumber(pressure.total)} signals`, children: _jsxs("div", { className: "overview-card-scroll pressure-panel", "data-smoke-marker": "warning-pressure", children: [_jsxs("div", { className: "pressure-grid", children: [pressureCell('Merge blockers', pressure.severe, pressure.severe ? 'failed, blocked, or source' : 'none'), pressureCell('Budget warnings', pressure.budget, pressure.budget ? 'context load' : 'within budget'), pressureCell('Timing gaps', telemetry.missingTimestampCount, telemetry.missingTimestampCount ? 'estimated spans' : 'current timestamps')] }), _jsx(BarRows, { rows: pressure.rows })] }) }), _jsx(Panel, { title: "Workspace audit", meta: `${text(audit.changedPathCount)} changed paths`, children: _jsxs("div", { className: "overview-card-scroll", children: [_jsxs("div", { className: "audit-list", children: [auditRow('Patch candidates', audit.changedPathCount), auditRow('Source violations', audit.sourceOwnershipViolationCount), auditRow('Generated noise', generatedNoiseCount(audit)), auditRow('Quarantined paths', audit.quarantinedChangedPathCount)] }), _jsx(ReasonClasses, { audit: audit })] }) }), _jsx(Panel, { title: "Recent activity", meta: `${text(events.length)} visible events`, children: _jsx("div", { className: "overview-card-scroll", children: _jsx(Events, { events: events.slice(-10) }) }) })] });
|
|
1134
|
+
}
|
|
1135
|
+
function snapshotTile(label, value, detail, tone) {
|
|
1136
|
+
return _jsxs("article", { className: `snapshot-tile ${tone}`, children: [_jsx("span", { children: label }), _jsx("b", { children: value }), _jsx("small", { children: detail })] });
|
|
1137
|
+
}
|
|
1138
|
+
function Success({ dashboard, jobs, success }) {
|
|
1139
|
+
const completedJobs = jobs.filter(isCompletedJob);
|
|
1140
|
+
const readyJobs = jobs.filter(isReadyJob);
|
|
1141
|
+
const cleanJobs = jobs.filter((job) => sourceOwnershipViolations(job).length === 0 && numberValue(job.quarantinedChangedPathCount) === 0);
|
|
1142
|
+
const semanticRows = semanticSuccessRows(dashboard.semantic);
|
|
1143
|
+
return _jsxs("div", { className: "success-layout", "data-scroll-id": "success", children: [_jsxs("section", { className: "success-summary", "data-smoke-marker": "success-summary", children: [successKpi('Completed jobs', success.completedCount, `${text(completedJobs.length)} visible`), successKpi('Ready patches', success.readyCount, 'direct apply candidates'), successKpi('Applied patches', success.appliedCount, `${text(success.applyTotalCount)} ledger total`), successKpi('Landed patches', success.landedCount, 'apply ledger entries'), successKpi('Source-clean jobs', success.cleanSourceCount, 'no source ownership flags'), successKpi('Evidence complete', success.evidenceCompleteCount, 'jobs with artifacts'), successKpi('Replay clean', success.semanticAcceptedClean, 'semantic replay accepted'), successKpi('Already applied', success.semanticAlreadyApplied, 'semantic edits recognized')] }), _jsxs("section", { className: "success-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Successful worker output" }), _jsxs("span", { children: [text(completedJobs.length), " visible completed jobs"] })] }), _jsx(SuccessRows, { jobs: completedJobs.slice(0, 24) })] }), _jsxs("section", { className: "success-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Ready or clean signals" }), _jsxs("span", { children: [text(readyJobs.length), " ready \u00B7 ", text(cleanJobs.length), " source-clean"] })] }), _jsx(BarRows, { rows: [
|
|
1144
|
+
{ label: 'Completed', value: success.completedCount, tone: 'good' },
|
|
1145
|
+
{ label: 'Ready patches', value: success.readyCount, tone: success.readyCount ? 'good' : 'neutral' },
|
|
1146
|
+
{ label: 'Applied patches', value: success.appliedCount, tone: success.applyFailedCount ? 'bad' : success.appliedCount ? 'good' : 'neutral' },
|
|
1147
|
+
{ label: 'Landed patches', value: success.landedCount, tone: success.landedCount ? 'good' : 'neutral' },
|
|
1148
|
+
{ label: 'Source-clean', value: success.cleanSourceCount, tone: 'good' },
|
|
1149
|
+
{ label: 'Evidence complete', value: success.evidenceCompleteCount, tone: 'good' }
|
|
1150
|
+
] })] }), semanticRows.length ? _jsxs("section", { className: "success-section wide", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Semantic success" }), _jsxs("span", { children: [text(success.semanticAcceptedClean + success.semanticAlreadyApplied), " accepted/applied"] })] }), _jsx(BarRows, { rows: semanticRows })] }) : null] });
|
|
1151
|
+
}
|
|
1152
|
+
function successKpi(label, value, detail) {
|
|
1153
|
+
return _jsxs("div", { className: "success-kpi", children: [_jsx("span", { children: label }), _jsx("b", { children: text(value) }), _jsx("small", { children: detail })] });
|
|
1154
|
+
}
|
|
1155
|
+
function SuccessRows({ jobs }) {
|
|
1156
|
+
if (!jobs.length)
|
|
1157
|
+
return _jsx("p", { className: "empty tight", children: "No completed jobs in this run." });
|
|
1158
|
+
return _jsx("div", { className: "success-list", children: jobs.map((job) => _jsxs("article", { className: "success-row", children: [_jsx("span", { className: "mono", children: text(job.id) }), _jsx("b", { children: text(job.disposition ?? job.mergeReadiness ?? job.status) }), _jsxs("small", { children: [text(laneOf(job)), " \u00B7 ", text(job.evidencePathCount), " evidence \u00B7 ", pathSummaryText(job)] })] })) });
|
|
1159
|
+
}
|
|
1160
|
+
function Jobs({ jobs }) {
|
|
1161
|
+
if (!jobs.length)
|
|
1162
|
+
return _jsx("div", { className: "table-scroll jobs-scroll", "data-scroll-id": "jobs", children: _jsx("p", { className: "empty", children: "No jobs are available for this run." }) });
|
|
1163
|
+
return _jsx("div", { className: "table-scroll jobs-scroll", "data-scroll-id": "jobs", children: _jsxs("table", { className: "jobs-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { className: "nowrap", children: "Risk" }), _jsx("th", { children: "Job / task" }), _jsx("th", { className: "nowrap", children: "Lane" }), _jsx("th", { className: "nowrap", children: "Status" }), _jsx("th", { children: "Bucket / disposition" }), _jsx("th", { children: "Paths" }), _jsx("th", { className: "nowrap", children: "Evidence" }), _jsx("th", { children: "Reasons" })] }) }), _jsx("tbody", { children: jobs.slice(0, 160).map((job) => _jsxs("tr", { className: `risk-${jobRisk(job)}`, children: [_jsx("td", { children: _jsx("span", { className: `risk-pill ${jobRisk(job)}`, children: jobRiskLabel(job) }) }), _jsxs("td", { children: [_jsx("span", { className: "mono strong", children: text(job.id) }), _jsx("small", { children: text(job.taskId ?? job.title) })] }), _jsx("td", { className: "lane-cell", title: text(job.lane), children: text(job.lane) }), _jsx("td", { className: "nowrap", children: text(job.status) }), _jsxs("td", { children: [_jsx("span", { children: text(job.bucket) }), _jsx("small", { children: text(job.disposition ?? job.mergeReadiness) })] }), _jsx("td", { children: pathSummary(job) }), _jsx("td", { className: "numeric", children: text(job.evidencePathCount) }), _jsx("td", { children: jobReasonText(job) })] })) })] }) });
|
|
1164
|
+
}
|
|
1165
|
+
function statCell(label, value) {
|
|
1166
|
+
return _jsxs("div", { className: "stat-cell", children: [_jsx("span", { children: label }), _jsx("b", { children: value })] });
|
|
1167
|
+
}
|
|
1168
|
+
function ContextPressure({ jobs }) {
|
|
1169
|
+
const summary = contextPressureSummary(jobs);
|
|
1170
|
+
const offenders = contextOffenderRows(jobs);
|
|
1171
|
+
return _jsxs("div", { className: "context-pressure", "data-smoke-marker": "context-pressure", children: [_jsxs("div", { className: "stat-grid", children: [statCell('Budget warnings', formatNumber(summary.warningCount)), statCell('Uncached input', summary.uncachedInputTokens ? formatNumber(summary.uncachedInputTokens) : '-'), statCell('Uncached / estimated', summary.uncachedRatio ? formatRatio(summary.uncachedRatio) : '-')] }), _jsx(BarRows, { rows: [
|
|
1172
|
+
{ label: 'Uncached input', value: summary.uncachedInputTokens, detail: formatNumber(summary.uncachedInputTokens), tone: summary.warningCount || summary.failedCount ? 'warn' : 'neutral' },
|
|
1173
|
+
{ label: 'Cached input', value: summary.cachedInputTokens, detail: formatNumber(summary.cachedInputTokens) },
|
|
1174
|
+
{ label: 'Actual total', value: summary.actualInputTokens, detail: formatNumber(summary.actualInputTokens) },
|
|
1175
|
+
{ label: 'Estimated tokens', value: summary.estimatedInputTokens, detail: formatNumber(summary.estimatedInputTokens) },
|
|
1176
|
+
{ label: 'P95 uncached', value: summary.p95UncachedInputTokens, detail: formatNumber(summary.p95UncachedInputTokens), tone: summary.p95UncachedInputTokens > summary.estimatedInputTokens / Math.max(1, jobs.length) ? 'warn' : 'neutral' }
|
|
1177
|
+
] }), _jsx("div", { className: "context-offender-list", "aria-label": "Top context offenders", children: offenders.length ? offenders.map((job) => _jsxs("article", { className: "context-offender-row", children: [_jsx("span", { className: "mono", children: text(job.id) }), _jsx("b", { children: formatNumber(job.uncachedInputTokens || job.actualInputTokens || job.estimatedInputTokens) }), _jsxs("small", { children: [text(job.lane), " \u00B7 ", job.statusLabel] })] })) : _jsx("p", { className: "empty tight", children: "No context pressure reported for visible jobs." }) })] });
|
|
1178
|
+
}
|
|
1179
|
+
function MiniCharts({ series, compact = false }) {
|
|
1180
|
+
if (!series.length)
|
|
1181
|
+
return _jsx("p", { className: "empty tight", children: "No chart data." });
|
|
1182
|
+
return _jsx("div", { className: compact ? 'chart-grid compact' : 'chart-grid', children: series.map((entry) => _jsxs("article", { className: `chart-card ${entry.tone ?? 'neutral'}`, "data-chart-id": entry.id, children: [_jsxs("div", { className: "chart-card-head", children: [_jsx("span", { children: entry.title }), _jsx("b", { children: entry.value })] }), _jsx(Sparkline, { points: entry.points, tone: entry.tone ?? 'neutral', label: `${entry.title}: ${entry.value}` }), compact ? null : _jsx("small", { children: chartDetailLabel(entry) })] })) });
|
|
1183
|
+
}
|
|
1184
|
+
function chartDetailLabel(entry) {
|
|
1185
|
+
const axis = entry.xLabel || entry.yLabel
|
|
1186
|
+
? `Y: ${entry.yLabel ?? 'value'} · X: ${entry.xLabel ?? 'series'}`
|
|
1187
|
+
: '';
|
|
1188
|
+
return axis ? `${axis} · ${entry.detail}` : entry.detail;
|
|
1189
|
+
}
|
|
1190
|
+
function RunHealthSummary({ health }) {
|
|
1191
|
+
return _jsxs("div", { className: "health-summary", "data-smoke-marker": "health-summary", children: [_jsxs("article", { className: `health-card ${health.tone}`, children: [_jsx("span", { children: "Run status" }), _jsx("b", { children: health.status }), _jsxs("small", { children: [formatPercent(health.completionRatio), " complete \u00B7 ", formatPercent(health.failureRatio), " failed/blocked"] })] }), _jsxs("article", { className: health.warningJobCount ? 'health-card warn' : 'health-card good', children: [_jsx("span", { children: "Warnings" }), _jsx("b", { children: text(health.warningJobCount) }), _jsxs("small", { children: [text(health.contextWarningJobCount), " context \u00B7 ", text(health.semanticCandidateJobCount), " semantic candidates"] })] }), _jsxs("article", { className: health.failedJobCount || health.blockedJobCount ? 'health-card bad' : 'health-card good', children: [_jsx("span", { children: "Success" }), _jsx("b", { children: text(health.healthyJobCount) }), _jsxs("small", { children: [text(health.readyToApplyJobCount), " ready \u00B7 ", text(health.semanticCleanJobCount), " semantic clean"] })] })] });
|
|
1192
|
+
}
|
|
1193
|
+
function Sparkline({ points, tone, label }) {
|
|
1194
|
+
const visible = points.length ? points : [{ label: 'No data', value: 0 }];
|
|
1195
|
+
const max = Math.max(1, ...visible.map((point) => point.value));
|
|
1196
|
+
return _jsx("div", { className: `sparkline aligned-chart ${tone}`, role: "img", "aria-label": label, "data-chart-points": text(visible.length), children: _jsx("div", { className: "spark-bars", children: visible.map((point) => _jsx("span", { className: `spark-bar ${point.tone ?? tone}`, "data-chart-tooltip": `${point.label}\n${point.detail ?? formatNumber(point.value)}`, title: `${point.label}: ${point.detail ?? formatNumber(point.value)}`, style: `height:${point.value ? Math.max(8, Math.round((point.value / max) * 100)) : 3}%` })) }) });
|
|
1197
|
+
}
|
|
1198
|
+
function BarRows({ rows }) {
|
|
1199
|
+
const max = Math.max(1, ...rows.map((row) => row.value));
|
|
1200
|
+
return _jsx("div", { className: "metric-bars", children: rows.map((row) => _jsxs("div", { className: "metric-bar-row", children: [_jsx("span", { className: "metric-bar-label", children: row.label }), _jsx("span", { className: "metric-bar-track", children: _jsx("span", { className: `metric-bar-fill ${row.tone ?? 'neutral'}`, style: `width:${row.value ? Math.max(3, Math.round((row.value / max) * 100)) : 0}%` }) }), _jsx("span", { className: "metric-bar-value", children: row.detail ?? formatNumber(row.value) })] })) });
|
|
1201
|
+
}
|
|
1202
|
+
function Quality({ jobs, attention, audit }) {
|
|
1203
|
+
const sourceOwnershipCount = audit.sourceOwnershipViolationCount;
|
|
1204
|
+
const ignoredCount = generatedNoiseCount(audit);
|
|
1205
|
+
const quarantinedCount = audit.quarantinedChangedPathCount;
|
|
1206
|
+
const blockerCount = attention.failedCount + attention.needsCoordinatorReviewCount + attention.staleCount + sourceOwnershipCount;
|
|
1207
|
+
const noiseCount = ignoredCount + quarantinedCount;
|
|
1208
|
+
return _jsxs("div", { className: "quality-layout", "data-smoke-marker": "quality-signal-panel", "data-scroll-id": "quality", children: [_jsxs("section", { className: "quality-summary", "data-smoke-marker": "quality-summary", children: [qualityKpi('Admission blockers', blockerCount, blockerCount ? 'Review before merge' : 'Clear'), qualityKpi('Generated workspace noise', noiseCount, noiseCount ? 'Separated from source' : 'None'), qualityKpi('Visible jobs', jobs.length, 'Current lane filter')] }), _jsxs("section", { className: "quality-group blocker", "data-smoke-marker": "quality-real-blockers", children: [_jsxs("div", { className: "quality-group-head", children: [_jsx("h3", { children: "Real blockers" }), _jsxs("span", { children: [text(blockerCount), " signals"] })] }), _jsxs("div", { className: "quality-signal-list", children: [qualitySignal('Failed jobs', attention.failedCount, 'Blocks admission', 'Worker failed, evidence failed, or merge readiness failed.', 'failed-jobs'), qualitySignal('Coordinator review', attention.needsCoordinatorReviewCount, 'Coordinator decision', 'Patch needs review or cannot be applied directly.', 'needs-coordinator'), qualitySignal('Stale', attention.staleCount, 'Rerun or rebase', 'Output was collected against an older source state.', 'stale'), qualitySignal('Source violations', sourceOwnershipCount, 'Out of scope source', 'Changed source paths are outside the job ownership boundary.', 'source-ownership')] })] }), _jsxs("section", { className: "quality-group noise", "data-smoke-marker": "quality-generated-noise", children: [_jsxs("div", { className: "quality-group-head", children: [_jsx("h3", { children: "Generated workspace noise" }), _jsxs("span", { children: [text(noiseCount), " separated paths"] })] }), _jsxs("div", { className: "quality-signal-list compact", children: [qualitySignal('Ignored', ignoredCount, 'Not a source blocker', 'Build, cache, dist, and dependency paths stay out of patch candidates.', 'ignored'), qualitySignal('Quarantined', quarantinedCount, 'Held back', 'Disallowed generated changes were separated from source patch review.', 'quarantined')] })] })] });
|
|
1209
|
+
}
|
|
1210
|
+
function qualityKpi(label, value, detail) {
|
|
1211
|
+
return _jsxs("div", { className: "quality-kpi", children: [_jsx("span", { children: label }), _jsx("b", { children: text(value) }), _jsx("small", { children: detail })] });
|
|
1212
|
+
}
|
|
1213
|
+
function qualitySignal(label, value, status, description, marker) {
|
|
1214
|
+
return _jsxs("article", { className: value ? 'quality-signal active' : 'quality-signal', "data-smoke-marker": `quality-signal-${marker}`, children: [_jsxs("div", { children: [_jsx("b", { children: label }), _jsx("p", { children: description })] }), _jsx("span", { className: "quality-signal-count", children: text(value) }), _jsx("span", { className: "quality-signal-status", children: value ? status : 'Clear' })] });
|
|
1215
|
+
}
|
|
1216
|
+
function Timeline({ dashboard, jobs, events, audit }) {
|
|
1217
|
+
const summary = timelineSummary(dashboard, jobs, events);
|
|
1218
|
+
const contribution = contributionGrid(dashboard, jobs, events);
|
|
1219
|
+
const bottlenecks = timelineBottlenecks(jobs, audit);
|
|
1220
|
+
const aria = `${text(summary.progressPercent)} percent terminal progress, ${text(summary.runningJobs)} running jobs, ${text(summary.attentionJobs)} attention jobs`;
|
|
1221
|
+
return _jsxs("div", { className: "timeline-layout", "data-scroll-id": "timeline", children: [_jsxs("section", { className: "timeline-section wide contribution-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Progress by day" }), _jsxs("span", { children: [text(contribution.activeDays), " active days \u00B7 ", text(contribution.totalDone), " done signals"] })] }), _jsx(ContributionGraph, { grid: contribution })] }), _jsxs("section", { className: "timeline-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Run progress" }), _jsxs("span", { children: [text(summary.terminalJobs), " / ", text(summary.totalJobs), " terminal"] })] }), _jsxs("div", { className: "timeline-progress-row", children: [_jsxs("div", { className: "timeline-progress-copy", children: [_jsxs("b", { children: [text(summary.progressPercent), "%"] }), _jsxs("span", { children: [text(summary.runningJobs), " running \u00B7 ", text(summary.attentionJobs), " attention"] })] }), _jsx("div", { className: "timeline-progress-track", role: "img", "aria-label": aria, children: _jsx("span", { className: "timeline-progress-fill", style: `width:${summary.progressPercent}%` }) })] }), _jsx(TimelineNote, { summary: summary }), _jsx(TimeSeriesSummary, { dashboard: dashboard })] }), _jsxs("section", { className: "timeline-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Event series" }), _jsx("span", { children: summary.points.length ? `${formatTime(summary.firstAt)} to ${formatTime(summary.lastAt)}` : 'no timestamps' })] }), _jsx(TimelineSeries, { summary: summary })] }), _jsxs("section", { className: "timeline-section", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Bottlenecks" }), _jsxs("span", { children: [text(bottlenecks.reduce((sum, row) => sum + row.value, 0)), " signals"] })] }), _jsx(BottleneckStrip, { rows: bottlenecks })] }), _jsxs("section", { className: "timeline-section wide", children: [_jsxs("div", { className: "metric-section-head", children: [_jsx("h3", { children: "Recent timeline" }), _jsxs("span", { children: [text(summary.points.length), " timestamped events"] })] }), _jsx(TimelineEventList, { points: summary.points })] })] });
|
|
1222
|
+
}
|
|
1223
|
+
function ContributionGraph({ grid, prominent = false }) {
|
|
1224
|
+
const weekTemplate = `repeat(${grid.weeks.length}, var(--contribution-cell))`;
|
|
1225
|
+
return _jsxs("div", { className: prominent ? 'contribution-card prominent' : 'contribution-card', "data-smoke-marker": "progress-by-day", children: [_jsxs("div", { className: "contribution-summary", children: [statCell('Calendar year', String(grid.year)), statCell('Done signals', formatNumber(grid.totalDone)), statCell('Active days', formatNumber(grid.activeDays)), statCell('Peak day', formatNumber(grid.maxCount))] }), _jsxs("div", { className: "contribution-scroll", "data-contribution-grid": "true", role: "img", "aria-label": `Daily progress activity across calendar year ${text(grid.year)}, January through December`, children: [_jsx("div", { className: "contribution-months", style: `grid-template-columns:${weekTemplate}`, children: grid.weeks.map((week, index) => _jsx("span", { children: contributionMonthLabel(week, index, grid.weeks) })) }), _jsxs("div", { className: "contribution-body", children: [_jsxs("div", { className: "contribution-weekdays", "aria-hidden": "true", children: [_jsx("span", {}), _jsx("span", { children: "Mon" }), _jsx("span", {}), _jsx("span", { children: "Wed" }), _jsx("span", {}), _jsx("span", { children: "Fri" }), _jsx("span", {})] }), _jsx("div", { className: "contribution-weeks", style: `grid-template-columns:${weekTemplate}`, children: grid.weeks.map((week) => _jsx("div", { className: "contribution-week", children: week.map((day) => day.inYear
|
|
1226
|
+
? _jsx("span", { className: `contribution-day level-${day.level}`, "data-contribution-tooltip": contributionTooltip(day), "aria-label": `${day.label}: ${formatNumber(day.count)} total activity` })
|
|
1227
|
+
: _jsx("span", { className: "contribution-day empty", "aria-hidden": "true" })) })) })] }), _jsxs("div", { className: "contribution-legend", "aria-hidden": "true", children: [_jsx("span", { children: "Less" }), _jsx("i", { className: "contribution-day level-0" }), _jsx("i", { className: "contribution-day level-1" }), _jsx("i", { className: "contribution-day level-2" }), _jsx("i", { className: "contribution-day level-3" }), _jsx("i", { className: "contribution-day level-4" }), _jsx("span", { children: "More" })] })] })] });
|
|
1228
|
+
}
|
|
1229
|
+
function contributionTooltip(day) {
|
|
1230
|
+
return `${day.label}\n${formatNumber(day.count)} total activity\n${formatNumber(day.completed)} done signals · ${formatNumber(day.events)} events`;
|
|
1231
|
+
}
|
|
1232
|
+
function TimelineNote({ summary }) {
|
|
1233
|
+
if (summary.exactTimingAvailable && !summary.eventWindowLimited)
|
|
1234
|
+
return _jsx("p", { className: "timeline-note", children: "Exact job spans are available for this dashboard snapshot." });
|
|
1235
|
+
const note = summary.points.length
|
|
1236
|
+
? 'Exact job spans are unavailable. Showing the recent event window plus current job states.'
|
|
1237
|
+
: 'No timestamped events are available for this run. Showing current job progress only.';
|
|
1238
|
+
return _jsx("p", { className: "timeline-note", children: note });
|
|
1239
|
+
}
|
|
1240
|
+
function TimeSeriesSummary({ dashboard }) {
|
|
1241
|
+
const summary = recordValue(dashboard.timeSeries?.summary);
|
|
1242
|
+
const pointCount = numberValue(summary.pointCount);
|
|
1243
|
+
if (!pointCount)
|
|
1244
|
+
return _jsx("p", { className: "empty tight", children: "No bucketed time-series metrics in this snapshot." });
|
|
1245
|
+
return _jsxs("div", { className: "time-series-summary", "data-smoke-marker": "time-series-summary", children: [statCell('Buckets', formatNumber(pointCount)), statCell('Terminal jobs', formatNumber(numberValue(summary.terminalJobCount))), statCell('Warnings', formatNumber(numberValue(summary.warningJobCount))), statCell('Missing time', formatNumber(numberValue(summary.missingTimestampJobCount)))] });
|
|
1246
|
+
}
|
|
1247
|
+
function TimelineSeries({ summary }) {
|
|
1248
|
+
if (!summary.points.length)
|
|
1249
|
+
return _jsx("p", { className: "empty tight", children: "No timestamped events for this run." });
|
|
1250
|
+
return _jsxs("div", { className: "timeline-series", role: "img", "aria-label": `Event timeline from ${formatTime(summary.firstAt)} to ${formatTime(summary.lastAt)}`, children: [_jsx("div", { className: "timeline-series-track", children: summary.points.slice(-48).map((point) => _jsx("span", { className: `timeline-point ${timelinePointTone(point)}`, style: `left:${timelinePointLeft(point, summary)}%`, title: `${formatTime(point.at)} ${point.type} ${point.jobId}` })) }), _jsxs("div", { className: "timeline-axis", "aria-hidden": "true", children: [_jsx("span", { children: formatTime(summary.firstAt) }), _jsx("span", { children: formatTime(summary.lastAt) })] })] });
|
|
1251
|
+
}
|
|
1252
|
+
function BottleneckStrip({ rows }) {
|
|
1253
|
+
const active = rows.filter((row) => row.value > 0);
|
|
1254
|
+
if (!active.length)
|
|
1255
|
+
return _jsx("p", { className: "empty tight", children: "No current bottlenecks in this run." });
|
|
1256
|
+
return _jsx("div", { className: "bottleneck-list", children: active.map((row) => _jsxs("div", { className: `bottleneck-row ${row.tone}`, children: [_jsx("span", { children: row.label }), _jsx("b", { children: text(row.value) }), _jsx("small", { children: row.detail })] })) });
|
|
1257
|
+
}
|
|
1258
|
+
function TimelineEventList({ points }) {
|
|
1259
|
+
if (!points.length)
|
|
1260
|
+
return _jsx("p", { className: "empty tight", children: "No recent event rows to show." });
|
|
1261
|
+
return _jsx("div", { className: "timeline-events", role: "list", children: points.slice(-24).reverse().map((point) => _jsxs("article", { className: `timeline-event ${timelinePointTone(point)}`, role: "listitem", children: [_jsx("time", { dateTime: new Date(point.at).toISOString(), children: formatTime(point.at) }), _jsxs("div", { children: [_jsx("b", { children: point.type }), _jsx("span", { children: point.message })] }), _jsxs("small", { children: [point.lane, " \u00B7 ", point.jobId, " \u00B7 ", text(point.progressPercent), "%"] })] })) });
|
|
1262
|
+
}
|
|
1263
|
+
function Events({ events }) {
|
|
1264
|
+
if (!events.length)
|
|
1265
|
+
return _jsx("p", { className: "empty", children: "No recent events for this run." });
|
|
1266
|
+
return _jsx("div", { className: "table-scroll compact", "data-scroll-id": "events", children: _jsxs("table", { className: "events-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Time" }), _jsx("th", { children: "Event" }), _jsx("th", { children: "Lane" }), _jsx("th", { children: "Job" }), _jsx("th", { children: "Message" })] }) }), _jsx("tbody", { children: events.slice(-36).reverse().map((event) => _jsxs("tr", { children: [_jsx("td", { children: formatTime(event.at) }), _jsx("td", { children: text(event.type) }), _jsx("td", { children: text(event.lane) }), _jsx("td", { className: "mono", children: text(event.jobId) }), _jsx("td", { children: text(event.message) })] })) })] }) });
|
|
1267
|
+
}
|
|
1268
|
+
function Sources({ entries }) {
|
|
1269
|
+
if (!entries.length)
|
|
1270
|
+
return _jsx("p", { className: "empty", children: "No source paths were reported by the dashboard snapshot." });
|
|
1271
|
+
return _jsx("div", { className: "table-scroll compact", "data-scroll-id": "sources", children: _jsx("table", { className: "sources-table", children: _jsx("tbody", { children: entries.map((entry) => _jsxs("tr", { children: [_jsx("th", { children: entry.label }), _jsx("td", { className: "mono", children: entry.value })] })) }) }) });
|
|
1272
|
+
}
|
|
1273
|
+
function RunContext({ routing, backlog }) {
|
|
1274
|
+
return _jsxs("div", { className: "context-grid", children: [_jsxs("dl", { className: "kv", children: [_jsx("dt", { children: "Policy" }), _jsx("dd", { children: text(routing?.policyId) }), _jsx("dt", { children: "Mode" }), _jsx("dd", { children: text(routing?.defaultMode) }), _jsx("dt", { children: "Prefer / avoid" }), _jsxs("dd", { children: [text(routing?.preferCount), " / ", text(routing?.avoidCount)] }), _jsx("dt", { children: "Feedback" }), _jsx("dd", { children: text(routing?.preferenceCount) })] }), _jsxs("dl", { className: "kv", children: [_jsx("dt", { children: "Backlog" }), _jsx("dd", { children: text(backlog?.id) }), _jsx("dt", { children: "Entries" }), _jsx("dd", { children: text(backlog?.entryCount) }), _jsx("dt", { children: "Ready" }), _jsx("dd", { children: text(backlog?.readyCount) }), _jsx("dt", { children: "Children" }), _jsx("dd", { children: text(Array.isArray(backlog?.childBacklogPaths) ? backlog?.childBacklogPaths.length : undefined) })] })] });
|
|
1275
|
+
}
|
|
1276
|
+
function auditSummary(dashboard, jobs) {
|
|
1277
|
+
const summary = dashboard.summary ?? {};
|
|
1278
|
+
const changedPathCount = numberValue(summary.changedPathCount)
|
|
1279
|
+
|| jobs.reduce((sum, job) => sum + numberValue(job.changedPathCount), 0);
|
|
1280
|
+
const ownershipViolationCount = numberValue(summary.ownershipViolationCount)
|
|
1281
|
+
|| jobs.reduce((sum, job) => sum + numberValue(job.ownershipViolationCount), 0);
|
|
1282
|
+
const sourceOwnershipViolationCount = numberValue(summary.sourceOwnershipViolationCount)
|
|
1283
|
+
|| jobs.reduce((sum, job) => sum + sourceOwnershipViolations(job).length, 0);
|
|
1284
|
+
const ignoredOwnershipViolationCount = numberValue(summary.ignoredOwnershipViolationCount)
|
|
1285
|
+
|| jobs.reduce((sum, job) => sum + ignoredOwnershipViolations(job).length, 0);
|
|
1286
|
+
const ignoredChangedPathCount = numberValue(summary.ignoredChangedPathCount)
|
|
1287
|
+
|| jobs.reduce((sum, job) => sum + numberValue(job.ignoredChangedPathCount), 0);
|
|
1288
|
+
const quarantinedChangedPathCount = numberValue(summary.quarantinedChangedPathCount)
|
|
1289
|
+
|| jobs.reduce((sum, job) => sum + numberValue(job.quarantinedChangedPathCount), 0);
|
|
1290
|
+
const reasonMap = new Map();
|
|
1291
|
+
for (const job of jobs) {
|
|
1292
|
+
for (const reason of stringArray(job.collectReasonClasses)) {
|
|
1293
|
+
reasonMap.set(reason, (reasonMap.get(reason) ?? 0) + 1);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
return {
|
|
1297
|
+
changedPathCount,
|
|
1298
|
+
ownershipViolationCount,
|
|
1299
|
+
sourceOwnershipViolationCount,
|
|
1300
|
+
ignoredOwnershipViolationCount,
|
|
1301
|
+
ignoredChangedPathCount,
|
|
1302
|
+
quarantinedChangedPathCount,
|
|
1303
|
+
reasonCounts: Array.from(reasonMap.entries())
|
|
1304
|
+
.map(([reason, count]) => ({ reason, count }))
|
|
1305
|
+
.sort((left, right) => right.count - left.count || left.reason.localeCompare(right.reason))
|
|
1306
|
+
.slice(0, 8)
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
function metric(label, value, tone, detail) {
|
|
1310
|
+
return _jsxs("article", { className: `metric ${tone}`, children: [_jsx("span", { children: label }), _jsx("b", { children: text(value) }), detail ? _jsx("small", { children: detail }) : null] });
|
|
1311
|
+
}
|
|
1312
|
+
function compactMetric(label, value) {
|
|
1313
|
+
return _jsxs("div", { className: "compact-metric", children: [_jsx("span", { children: label }), _jsx("b", { children: text(value) })] });
|
|
1314
|
+
}
|
|
1315
|
+
function auditRow(label, value) {
|
|
1316
|
+
return _jsxs("div", { className: "audit-row", children: [_jsx("span", { children: label }), _jsx("b", { children: text(value) })] });
|
|
1317
|
+
}
|
|
1318
|
+
function pressureCell(label, value, detail) {
|
|
1319
|
+
return _jsxs("div", { className: "pressure-card", children: [_jsx("span", { children: label }), _jsx("b", { children: text(value) }), _jsx("small", { children: detail })] });
|
|
1320
|
+
}
|
|
1321
|
+
function generatedNoiseCount(audit) {
|
|
1322
|
+
return audit.ignoredOwnershipViolationCount + audit.ignoredChangedPathCount;
|
|
1323
|
+
}
|
|
1324
|
+
function ReasonClasses({ audit }) {
|
|
1325
|
+
if (!audit.reasonCounts.length)
|
|
1326
|
+
return _jsx("p", { className: "empty tight", children: "No collect reason classes." });
|
|
1327
|
+
return _jsx("div", { className: "reason-list", children: audit.reasonCounts.map((entry) => _jsxs("div", { className: "reason-row", children: [_jsx("span", { children: entry.reason }), _jsx("b", { children: text(entry.count) })] })) });
|
|
1328
|
+
}
|
|
1329
|
+
function SimpleRows({ rows }) {
|
|
1330
|
+
if (!rows.length)
|
|
1331
|
+
return _jsx("p", { className: "empty tight", children: "No rows for this run." });
|
|
1332
|
+
return _jsx("div", { className: "simple-rows", children: rows.map((row) => _jsxs("article", { className: "simple-row", children: [_jsxs("div", { children: [_jsx("b", { children: row.label }), _jsx("small", { children: row.detail })] }), _jsx("span", { children: row.value })] })) });
|
|
1333
|
+
}
|
|
1334
|
+
function humanActionRows(jobs, audit, dashboard) {
|
|
1335
|
+
void audit;
|
|
1336
|
+
return snapshotHumanActionRows(dashboard, jobs);
|
|
1337
|
+
}
|
|
1338
|
+
function snapshotHumanActionRows(dashboard, jobs) {
|
|
1339
|
+
if (!dashboard || !Array.isArray(dashboard.humanActions))
|
|
1340
|
+
return [];
|
|
1341
|
+
const visibleJobIds = new Set(jobs.map((job) => textValue(job.id ?? job.taskId, '')).filter(Boolean));
|
|
1342
|
+
const answeredCodes = answeredHumanActionCodes(dashboard);
|
|
1343
|
+
const rows = dashboard.humanActions
|
|
1344
|
+
.map(snapshotHumanActionRow)
|
|
1345
|
+
.filter((row) => Boolean(row))
|
|
1346
|
+
.filter(isExplicitAgentQuestion)
|
|
1347
|
+
.filter(isOpenHumanActionRow)
|
|
1348
|
+
.filter((row) => !answeredCodes.has(row.code))
|
|
1349
|
+
.filter((row) => !row.jobId || visibleJobIds.has(row.jobId));
|
|
1350
|
+
return dedupeHumanActionRows(rows)
|
|
1351
|
+
.sort((left, right) => priorityRank(left.priority) - priorityRank(right.priority) || left.code.localeCompare(right.code));
|
|
1352
|
+
}
|
|
1353
|
+
function answeredHumanActionCodes(dashboard) {
|
|
1354
|
+
return new Set(Array.isArray(dashboard.humanActionAnswers)
|
|
1355
|
+
? dashboard.humanActionAnswers.map((answer) => textValue(answer.code, '')).filter(Boolean)
|
|
1356
|
+
: []);
|
|
1357
|
+
}
|
|
1358
|
+
function pruneResolvedHumanActionDrafts(dashboard) {
|
|
1359
|
+
const openCodes = new Set(snapshotHumanActionRows(dashboard, dashboard.jobs).map((row) => row.code));
|
|
1360
|
+
for (const code of humanAnswerDrafts.keys())
|
|
1361
|
+
if (!openCodes.has(code))
|
|
1362
|
+
humanAnswerDrafts.delete(code);
|
|
1363
|
+
for (const code of humanAnswerStates.keys())
|
|
1364
|
+
if (!openCodes.has(code))
|
|
1365
|
+
humanAnswerStates.delete(code);
|
|
1366
|
+
}
|
|
1367
|
+
function snapshotHumanActionRow(value) {
|
|
1368
|
+
const code = textValue(value.code, '');
|
|
1369
|
+
if (!code)
|
|
1370
|
+
return undefined;
|
|
1371
|
+
const type = normalizeHumanActionType(textValue(value.type, 'concern'));
|
|
1372
|
+
const priority = normalizeHumanActionPriority(textValue(value.priority, 'info'));
|
|
1373
|
+
const title = textValue(value.title, code);
|
|
1374
|
+
const question = textValue(value.question ?? value.prompt ?? value.detail ?? value.text, title);
|
|
1375
|
+
const detail = textValue(value.detail ?? value.context ?? value.text, question);
|
|
1376
|
+
return {
|
|
1377
|
+
id: textValue(value.id, code),
|
|
1378
|
+
code,
|
|
1379
|
+
status: textValue(value.status, 'open'),
|
|
1380
|
+
priority,
|
|
1381
|
+
type,
|
|
1382
|
+
title,
|
|
1383
|
+
question,
|
|
1384
|
+
scope: textValue(value.scope ?? value.lane, 'workspace'),
|
|
1385
|
+
detail,
|
|
1386
|
+
why: textValue(value.why ?? value.reason, ''),
|
|
1387
|
+
requestedAnswer: textValue(value.requestedAnswer ?? value.answerFormat ?? value.expectedAnswer, ''),
|
|
1388
|
+
defaultAction: textValue(value.defaultAction, 'Answer in Codex so the coordinator can resolve the item.'),
|
|
1389
|
+
askedBy: textValue(value.askedBy ?? value.agentId ?? value.jobId, ''),
|
|
1390
|
+
source: textValue(value.source, 'dashboard'),
|
|
1391
|
+
jobId: textValue(value.jobId, ''),
|
|
1392
|
+
lane: textValue(value.lane, ''),
|
|
1393
|
+
options: humanActionOptions(value.options)
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
function humanActionOptions(value) {
|
|
1397
|
+
if (!Array.isArray(value))
|
|
1398
|
+
return [];
|
|
1399
|
+
return value.flatMap((entry) => {
|
|
1400
|
+
if (typeof entry === 'string')
|
|
1401
|
+
return [{ label: entry }];
|
|
1402
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
1403
|
+
return [];
|
|
1404
|
+
const option = entry;
|
|
1405
|
+
const label = textValue(option.label ?? option.title ?? option.value, '');
|
|
1406
|
+
if (!label)
|
|
1407
|
+
return [];
|
|
1408
|
+
const detail = textValue(option.detail ?? option.description ?? option.impact, '');
|
|
1409
|
+
return [{ label, ...(detail ? { detail } : {}) }];
|
|
1410
|
+
}).slice(0, 6);
|
|
1411
|
+
}
|
|
1412
|
+
function normalizeHumanActionType(value) {
|
|
1413
|
+
if (value === 'question' || value === 'concern' || value === 'review' || value === 'approval')
|
|
1414
|
+
return value;
|
|
1415
|
+
return 'concern';
|
|
1416
|
+
}
|
|
1417
|
+
function isExplicitAgentQuestion(row) {
|
|
1418
|
+
return row.type === 'question' && Boolean(row.question.trim());
|
|
1419
|
+
}
|
|
1420
|
+
function normalizeHumanActionPriority(value) {
|
|
1421
|
+
if (value === 'blocking' || value === 'important' || value === 'info')
|
|
1422
|
+
return value;
|
|
1423
|
+
return 'info';
|
|
1424
|
+
}
|
|
1425
|
+
function isOpenHumanActionRow(row) {
|
|
1426
|
+
const status = (row.status ?? 'open').toLowerCase();
|
|
1427
|
+
return !['answered', 'resolved', 'dismissed', 'cancelled', 'canceled', 'closed'].includes(status);
|
|
1428
|
+
}
|
|
1429
|
+
function dedupeHumanActionRows(rows) {
|
|
1430
|
+
const out = new Map();
|
|
1431
|
+
for (const row of rows)
|
|
1432
|
+
if (!out.has(row.code))
|
|
1433
|
+
out.set(row.code, row);
|
|
1434
|
+
return Array.from(out.values()).slice(0, 60);
|
|
1435
|
+
}
|
|
1436
|
+
function priorityRank(priority) {
|
|
1437
|
+
if (priority === 'blocking')
|
|
1438
|
+
return 0;
|
|
1439
|
+
if (priority === 'important')
|
|
1440
|
+
return 1;
|
|
1441
|
+
return 2;
|
|
1442
|
+
}
|
|
1443
|
+
function successLikeJobCount(jobs) {
|
|
1444
|
+
return jobs.filter((job) => isCompletedJob(job) && !isFailedJob(job) && !isStaleJob(job)).length;
|
|
1445
|
+
}
|
|
1446
|
+
function laneLoadTone(lane) {
|
|
1447
|
+
if (lane.failedCount || lane.blockedCount)
|
|
1448
|
+
return 'bad';
|
|
1449
|
+
if (lane.staleCount || lane.needsCoordinatorReviewCount)
|
|
1450
|
+
return 'warn';
|
|
1451
|
+
if (lane.completedCount && lane.completedCount === lane.jobCount)
|
|
1452
|
+
return 'good';
|
|
1453
|
+
return 'neutral';
|
|
1454
|
+
}
|
|
1455
|
+
function captureScrollPositions() {
|
|
1456
|
+
root?.querySelectorAll('[data-scroll-id]').forEach((element) => {
|
|
1457
|
+
const id = element.dataset.scrollId;
|
|
1458
|
+
if (!id)
|
|
1459
|
+
return;
|
|
1460
|
+
scrollPositions.set(id, { left: element.scrollLeft, top: element.scrollTop });
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
function restoreScrollPositions() {
|
|
1464
|
+
window.requestAnimationFrame(() => {
|
|
1465
|
+
root?.querySelectorAll('[data-scroll-id]').forEach((element) => {
|
|
1466
|
+
const id = element.dataset.scrollId;
|
|
1467
|
+
const position = id ? scrollPositions.get(id) : undefined;
|
|
1468
|
+
if (!position)
|
|
1469
|
+
return;
|
|
1470
|
+
element.scrollLeft = position.left;
|
|
1471
|
+
element.scrollTop = position.top;
|
|
1472
|
+
});
|
|
1473
|
+
if (pendingFocusTab) {
|
|
1474
|
+
const focusTab = pendingFocusTab;
|
|
1475
|
+
pendingFocusTab = undefined;
|
|
1476
|
+
contentTabButtons().find((tab) => tab.dataset.contentTab === focusTab)?.focus();
|
|
1477
|
+
}
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
function selectContentTab(tab) {
|
|
1481
|
+
selectedTab = tab;
|
|
1482
|
+
writeRouteState();
|
|
1483
|
+
if (currentDashboard)
|
|
1484
|
+
renderDashboard(currentDashboard);
|
|
1485
|
+
}
|
|
1486
|
+
function openTaskCard(id) {
|
|
1487
|
+
if (!id)
|
|
1488
|
+
return;
|
|
1489
|
+
if (selectedTaskCardId !== id)
|
|
1490
|
+
scrollPositions.delete(taskDialogScrollId(id));
|
|
1491
|
+
selectedTaskCardId = id;
|
|
1492
|
+
selectedTicketId = undefined;
|
|
1493
|
+
if (!currentDashboard)
|
|
1494
|
+
return;
|
|
1495
|
+
const item = taskBoardItems(currentDashboard, currentDashboard.jobs).find((job) => taskCardId(job) === id);
|
|
1496
|
+
if (item)
|
|
1497
|
+
selectedTicketId = ticketId(item);
|
|
1498
|
+
writeRouteState();
|
|
1499
|
+
if (item)
|
|
1500
|
+
void fetchTaskDetails(item);
|
|
1501
|
+
renderDashboard(currentDashboard);
|
|
1502
|
+
}
|
|
1503
|
+
function taskDialogScrollId(id) {
|
|
1504
|
+
return `task-dialog-${id}`;
|
|
1505
|
+
}
|
|
1506
|
+
async function fetchTaskDetails(job) {
|
|
1507
|
+
const id = taskCardId(job);
|
|
1508
|
+
if (!id || taskDetailsCache.has(id) || taskDetailsPending.has(id))
|
|
1509
|
+
return;
|
|
1510
|
+
if (job.boardKind === 'backlog') {
|
|
1511
|
+
taskDetailsCache.set(id, { ok: true, jobId: id, files: [], commandsPassed: [], commandsFailed: [], evidenceArtifacts: [] });
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
taskDetailsPending.add(id);
|
|
1515
|
+
try {
|
|
1516
|
+
const response = await fetch(`/api/task-details?id=${encodeURIComponent(id)}`);
|
|
1517
|
+
const details = await response.json();
|
|
1518
|
+
taskDetailsCache.set(id, details);
|
|
1519
|
+
}
|
|
1520
|
+
catch (error) {
|
|
1521
|
+
taskDetailsCache.set(id, {
|
|
1522
|
+
ok: false,
|
|
1523
|
+
jobId: id,
|
|
1524
|
+
files: [],
|
|
1525
|
+
commandsPassed: [],
|
|
1526
|
+
commandsFailed: [],
|
|
1527
|
+
evidenceArtifacts: [],
|
|
1528
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
finally {
|
|
1532
|
+
taskDetailsPending.delete(id);
|
|
1533
|
+
if (selectedTaskCardId === id && currentDashboard)
|
|
1534
|
+
renderDashboard(currentDashboard);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
async function revealArtifactInFinder(pathValue) {
|
|
1538
|
+
try {
|
|
1539
|
+
const response = await fetch('/api/artifact/reveal', {
|
|
1540
|
+
method: 'POST',
|
|
1541
|
+
headers: { 'content-type': 'application/json' },
|
|
1542
|
+
body: JSON.stringify({ path: pathValue })
|
|
1543
|
+
});
|
|
1544
|
+
const result = await response.json();
|
|
1545
|
+
if (!response.ok || !result.ok)
|
|
1546
|
+
console.warn('Unable to reveal artifact in Finder:', result.error ?? response.statusText);
|
|
1547
|
+
}
|
|
1548
|
+
catch (error) {
|
|
1549
|
+
console.warn('Unable to reveal artifact in Finder:', error);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
async function submitHumanActionAnswer(form) {
|
|
1553
|
+
const code = form.dataset.humanAnswerCode ?? '';
|
|
1554
|
+
const field = form.querySelector('textarea[name="answer"]');
|
|
1555
|
+
const answer = field?.value.trim() ?? '';
|
|
1556
|
+
if (!code)
|
|
1557
|
+
return;
|
|
1558
|
+
if (!answer) {
|
|
1559
|
+
humanAnswerStates.set(code, { status: 'error', message: 'Enter an answer first.' });
|
|
1560
|
+
if (currentDashboard)
|
|
1561
|
+
renderDashboard(currentDashboard);
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
humanAnswerDrafts.set(code, answer);
|
|
1565
|
+
humanAnswerStates.set(code, { status: 'submitting', message: 'Submitting...' });
|
|
1566
|
+
if (currentDashboard)
|
|
1567
|
+
renderDashboard(currentDashboard);
|
|
1568
|
+
try {
|
|
1569
|
+
const response = await fetch('/api/human-actions/answer', {
|
|
1570
|
+
method: 'POST',
|
|
1571
|
+
headers: { 'content-type': 'application/json' },
|
|
1572
|
+
body: JSON.stringify({ code, answer })
|
|
1573
|
+
});
|
|
1574
|
+
const result = await response.json();
|
|
1575
|
+
if (!response.ok || !result.ok)
|
|
1576
|
+
throw new Error(result.error ?? response.statusText);
|
|
1577
|
+
humanAnswerDrafts.delete(code);
|
|
1578
|
+
humanAnswerStates.set(code, { status: 'submitted', message: 'Answer submitted.' });
|
|
1579
|
+
void refresh();
|
|
1580
|
+
}
|
|
1581
|
+
catch (error) {
|
|
1582
|
+
humanAnswerStates.set(code, { status: 'error', message: error instanceof Error ? error.message : String(error) });
|
|
1583
|
+
}
|
|
1584
|
+
finally {
|
|
1585
|
+
if (currentDashboard)
|
|
1586
|
+
renderDashboard(currentDashboard);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
async function copyTextToClipboard(value, target) {
|
|
1590
|
+
let copied = false;
|
|
1591
|
+
try {
|
|
1592
|
+
if (navigator.clipboard?.writeText) {
|
|
1593
|
+
await navigator.clipboard.writeText(value);
|
|
1594
|
+
copied = true;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
catch {
|
|
1598
|
+
copied = fallbackCopyText(value);
|
|
1599
|
+
}
|
|
1600
|
+
if (!copied)
|
|
1601
|
+
copied = fallbackCopyText(value);
|
|
1602
|
+
if (!target)
|
|
1603
|
+
return;
|
|
1604
|
+
const previousText = target.textContent ?? '';
|
|
1605
|
+
target.dataset.copyState = copied ? 'copied' : 'failed';
|
|
1606
|
+
target.textContent = copied ? 'Copied' : 'Copy failed';
|
|
1607
|
+
window.setTimeout(() => {
|
|
1608
|
+
target.textContent = previousText;
|
|
1609
|
+
delete target.dataset.copyState;
|
|
1610
|
+
}, 900);
|
|
1611
|
+
}
|
|
1612
|
+
function fallbackCopyText(value) {
|
|
1613
|
+
const field = document.createElement('textarea');
|
|
1614
|
+
field.value = value;
|
|
1615
|
+
field.setAttribute('readonly', 'true');
|
|
1616
|
+
field.style.position = 'fixed';
|
|
1617
|
+
field.style.left = '-9999px';
|
|
1618
|
+
field.style.top = '0';
|
|
1619
|
+
document.body.append(field);
|
|
1620
|
+
field.focus();
|
|
1621
|
+
field.select();
|
|
1622
|
+
let copied = false;
|
|
1623
|
+
try {
|
|
1624
|
+
copied = document.execCommand('copy');
|
|
1625
|
+
}
|
|
1626
|
+
catch {
|
|
1627
|
+
copied = false;
|
|
1628
|
+
}
|
|
1629
|
+
finally {
|
|
1630
|
+
field.remove();
|
|
1631
|
+
}
|
|
1632
|
+
return copied;
|
|
1633
|
+
}
|
|
1634
|
+
function closeTaskDialog() {
|
|
1635
|
+
selectedTaskCardId = undefined;
|
|
1636
|
+
selectedTicketId = undefined;
|
|
1637
|
+
writeRouteState();
|
|
1638
|
+
if (currentDashboard)
|
|
1639
|
+
renderDashboard(currentDashboard);
|
|
1640
|
+
}
|
|
1641
|
+
function selectedTaskItem(dashboard, jobs) {
|
|
1642
|
+
if (!selectedTaskCardId && !selectedTicketId)
|
|
1643
|
+
return undefined;
|
|
1644
|
+
const items = taskBoardItems(dashboard, jobs);
|
|
1645
|
+
const byTask = selectedTaskCardId ? items.find((job) => taskCardId(job) === selectedTaskCardId) : undefined;
|
|
1646
|
+
const item = byTask ?? (selectedTicketId ? items.find((job) => ticketId(job) === selectedTicketId) : undefined);
|
|
1647
|
+
if (!item) {
|
|
1648
|
+
selectedTaskCardId = undefined;
|
|
1649
|
+
selectedTicketId = undefined;
|
|
1650
|
+
window.setTimeout(writeRouteState, 0);
|
|
1651
|
+
return undefined;
|
|
1652
|
+
}
|
|
1653
|
+
selectedTaskCardId = taskCardId(item);
|
|
1654
|
+
selectedTicketId = ticketId(item);
|
|
1655
|
+
return item;
|
|
1656
|
+
}
|
|
1657
|
+
function routeStateFromLocation() {
|
|
1658
|
+
const raw = window.location.hash.replace(/^#/, '');
|
|
1659
|
+
const params = new URLSearchParams(raw.includes('=') ? raw : raw ? `tab=${raw}` : '');
|
|
1660
|
+
return {
|
|
1661
|
+
tab: asContentTab(params.get('tab')),
|
|
1662
|
+
taskId: textValue(params.get('task'), '') || undefined,
|
|
1663
|
+
ticket: textValue(params.get('ticket'), '') || undefined
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
function writeRouteState() {
|
|
1667
|
+
const params = new URLSearchParams();
|
|
1668
|
+
params.set('tab', selectedTab);
|
|
1669
|
+
if (selectedTaskCardId)
|
|
1670
|
+
params.set('task', selectedTaskCardId);
|
|
1671
|
+
if (selectedTicketId)
|
|
1672
|
+
params.set('ticket', selectedTicketId);
|
|
1673
|
+
const next = `${window.location.pathname}${window.location.search}#${params.toString()}`;
|
|
1674
|
+
if (`${window.location.pathname}${window.location.search}${window.location.hash}` !== next) {
|
|
1675
|
+
window.history.replaceState(null, '', next);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
function contentTabButtons() {
|
|
1679
|
+
return Array.from(root?.querySelectorAll('[data-content-tab]') ?? []);
|
|
1680
|
+
}
|
|
1681
|
+
function isTabNavigationKey(key) {
|
|
1682
|
+
return key === 'ArrowRight' || key === 'ArrowLeft' || key === 'Home' || key === 'End';
|
|
1683
|
+
}
|
|
1684
|
+
function nextTabIndex(key, currentIndex, count) {
|
|
1685
|
+
if (key === 'Home')
|
|
1686
|
+
return 0;
|
|
1687
|
+
if (key === 'End')
|
|
1688
|
+
return Math.max(0, count - 1);
|
|
1689
|
+
if (key === 'ArrowLeft')
|
|
1690
|
+
return (currentIndex - 1 + count) % count;
|
|
1691
|
+
return (currentIndex + 1) % count;
|
|
1692
|
+
}
|
|
1693
|
+
function asContentTab(value) {
|
|
1694
|
+
if (value === 'agents')
|
|
1695
|
+
return 'swarm';
|
|
1696
|
+
return contentTabs.some((tab) => tab.id === value) ? value : 'work';
|
|
1697
|
+
}
|
|
1698
|
+
function contributionTooltipTarget(value) {
|
|
1699
|
+
return value instanceof Element
|
|
1700
|
+
? value.closest('[data-contribution-tooltip]') ?? undefined
|
|
1701
|
+
: undefined;
|
|
1702
|
+
}
|
|
1703
|
+
function chartTooltipTarget(value) {
|
|
1704
|
+
return value instanceof Element
|
|
1705
|
+
? value.closest('[data-chart-tooltip]') ?? undefined
|
|
1706
|
+
: undefined;
|
|
1707
|
+
}
|
|
1708
|
+
function contributionGridTarget(value) {
|
|
1709
|
+
return value instanceof Element
|
|
1710
|
+
? value.closest('[data-contribution-grid]') ?? undefined
|
|
1711
|
+
: undefined;
|
|
1712
|
+
}
|
|
1713
|
+
function nearestContributionTarget(event) {
|
|
1714
|
+
const exact = contributionTooltipTarget(event.target);
|
|
1715
|
+
if (exact)
|
|
1716
|
+
return exact;
|
|
1717
|
+
const grid = contributionGridTarget(event.target);
|
|
1718
|
+
if (!grid)
|
|
1719
|
+
return undefined;
|
|
1720
|
+
const weeks = grid.querySelector('.contribution-weeks');
|
|
1721
|
+
if (!weeks)
|
|
1722
|
+
return undefined;
|
|
1723
|
+
const weeksRect = weeks.getBoundingClientRect();
|
|
1724
|
+
if (event.clientX < weeksRect.left ||
|
|
1725
|
+
event.clientX > weeksRect.right ||
|
|
1726
|
+
event.clientY < weeksRect.top ||
|
|
1727
|
+
event.clientY > weeksRect.bottom) {
|
|
1728
|
+
return undefined;
|
|
1729
|
+
}
|
|
1730
|
+
let best;
|
|
1731
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
1732
|
+
for (const target of Array.from(weeks.querySelectorAll('[data-contribution-tooltip]'))) {
|
|
1733
|
+
const rect = target.getBoundingClientRect();
|
|
1734
|
+
const dx = event.clientX < rect.left ? rect.left - event.clientX : event.clientX > rect.right ? event.clientX - rect.right : 0;
|
|
1735
|
+
const dy = event.clientY < rect.top ? rect.top - event.clientY : event.clientY > rect.bottom ? event.clientY - rect.bottom : 0;
|
|
1736
|
+
const distance = dx * dx + dy * dy;
|
|
1737
|
+
if (distance >= bestDistance)
|
|
1738
|
+
continue;
|
|
1739
|
+
best = target;
|
|
1740
|
+
bestDistance = distance;
|
|
1741
|
+
}
|
|
1742
|
+
return best;
|
|
1743
|
+
}
|
|
1744
|
+
function nearestChartPopoverTarget(event) {
|
|
1745
|
+
return chartTooltipTarget(event.target) ?? nearestContributionTarget(event);
|
|
1746
|
+
}
|
|
1747
|
+
function isStillInsideContributionGrid(event) {
|
|
1748
|
+
const grid = contributionGridTarget(event.target);
|
|
1749
|
+
return Boolean(grid && event.relatedTarget instanceof Node && grid.contains(event.relatedTarget));
|
|
1750
|
+
}
|
|
1751
|
+
function showChartPopover(target, pointerX, pointerY) {
|
|
1752
|
+
const popover = ensureChartPopover();
|
|
1753
|
+
popover.textContent = target.dataset.chartTooltip ?? target.dataset.contributionTooltip ?? '';
|
|
1754
|
+
popover.classList.add('visible');
|
|
1755
|
+
positionChartPopover(target, pointerX, pointerY);
|
|
1756
|
+
}
|
|
1757
|
+
function hideChartPopover() {
|
|
1758
|
+
activeContributionTarget = undefined;
|
|
1759
|
+
chartPopover?.classList.remove('visible', 'below');
|
|
1760
|
+
}
|
|
1761
|
+
function ensureChartPopover() {
|
|
1762
|
+
if (chartPopover && document.body.contains(chartPopover))
|
|
1763
|
+
return chartPopover;
|
|
1764
|
+
chartPopover = document.createElement('div');
|
|
1765
|
+
chartPopover.className = 'chart-popover';
|
|
1766
|
+
chartPopover.setAttribute('role', 'tooltip');
|
|
1767
|
+
document.body.appendChild(chartPopover);
|
|
1768
|
+
return chartPopover;
|
|
1769
|
+
}
|
|
1770
|
+
function positionChartPopover(target, pointerX, pointerY) {
|
|
1771
|
+
if (!chartPopover)
|
|
1772
|
+
return;
|
|
1773
|
+
const rect = target.getBoundingClientRect();
|
|
1774
|
+
const width = Math.min(240, Math.max(180, chartPopover.offsetWidth || 200));
|
|
1775
|
+
const leftSource = pointerX ?? rect.left + rect.width / 2;
|
|
1776
|
+
const left = Math.max(12 + width / 2, Math.min(window.innerWidth - 12 - width / 2, leftSource));
|
|
1777
|
+
const aboveTop = (pointerY ?? rect.top) - 12;
|
|
1778
|
+
const below = aboveTop < 42;
|
|
1779
|
+
const top = below ? rect.bottom + 10 : aboveTop;
|
|
1780
|
+
chartPopover.style.left = `${left}px`;
|
|
1781
|
+
chartPopover.style.top = `${top}px`;
|
|
1782
|
+
chartPopover.style.maxWidth = `${width}px`;
|
|
1783
|
+
chartPopover.classList.toggle('below', below);
|
|
1784
|
+
}
|
|
1785
|
+
function laneRollups(dashboard) {
|
|
1786
|
+
const rows = new Map();
|
|
1787
|
+
for (const lane of dashboard.lanes) {
|
|
1788
|
+
const id = textValue(lane.id, 'unassigned');
|
|
1789
|
+
rows.set(id, {
|
|
1790
|
+
id,
|
|
1791
|
+
jobCount: numberValue(lane.jobCount),
|
|
1792
|
+
completedCount: numberValue(lane.completedCount),
|
|
1793
|
+
failedCount: numberValue(lane.failedCount),
|
|
1794
|
+
runningCount: numberValue(lane.runningCount),
|
|
1795
|
+
blockedCount: 0,
|
|
1796
|
+
needsCoordinatorReviewCount: 0,
|
|
1797
|
+
staleCount: 0,
|
|
1798
|
+
evidenceCount: 0,
|
|
1799
|
+
eventCount: 0
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
if (dashboard.jobs.length > 0) {
|
|
1803
|
+
for (const row of rows.values()) {
|
|
1804
|
+
row.jobCount = 0;
|
|
1805
|
+
row.completedCount = 0;
|
|
1806
|
+
row.failedCount = 0;
|
|
1807
|
+
row.runningCount = 0;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
for (const job of dashboard.jobs) {
|
|
1811
|
+
const lane = ensureLane(rows, laneOf(job));
|
|
1812
|
+
lane.jobCount += 1;
|
|
1813
|
+
if (normalized(job.status) === 'completed')
|
|
1814
|
+
lane.completedCount += 1;
|
|
1815
|
+
if (normalized(job.status) === 'running')
|
|
1816
|
+
lane.runningCount += 1;
|
|
1817
|
+
if (isFailedJob(job))
|
|
1818
|
+
lane.failedCount += 1;
|
|
1819
|
+
if (isBlockedJob(job))
|
|
1820
|
+
lane.blockedCount += 1;
|
|
1821
|
+
if (isNeedsCoordinatorReviewJob(job))
|
|
1822
|
+
lane.needsCoordinatorReviewCount += 1;
|
|
1823
|
+
if (isStaleJob(job))
|
|
1824
|
+
lane.staleCount += 1;
|
|
1825
|
+
lane.evidenceCount += numberValue(job.evidencePathCount);
|
|
1826
|
+
}
|
|
1827
|
+
for (const event of dashboard.events) {
|
|
1828
|
+
const laneId = textValue(event.lane, '');
|
|
1829
|
+
if (!laneId)
|
|
1830
|
+
continue;
|
|
1831
|
+
ensureLane(rows, laneId).eventCount += 1;
|
|
1832
|
+
}
|
|
1833
|
+
return Array.from(rows.values()).sort((left, right) => {
|
|
1834
|
+
const risk = (right.failedCount + right.staleCount + right.needsCoordinatorReviewCount) - (left.failedCount + left.staleCount + left.needsCoordinatorReviewCount);
|
|
1835
|
+
return risk || left.id.localeCompare(right.id);
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
function ensureLane(rows, id) {
|
|
1839
|
+
const existing = rows.get(id);
|
|
1840
|
+
if (existing)
|
|
1841
|
+
return existing;
|
|
1842
|
+
const row = {
|
|
1843
|
+
id,
|
|
1844
|
+
jobCount: 0,
|
|
1845
|
+
completedCount: 0,
|
|
1846
|
+
failedCount: 0,
|
|
1847
|
+
runningCount: 0,
|
|
1848
|
+
blockedCount: 0,
|
|
1849
|
+
needsCoordinatorReviewCount: 0,
|
|
1850
|
+
staleCount: 0,
|
|
1851
|
+
evidenceCount: 0,
|
|
1852
|
+
eventCount: 0
|
|
1853
|
+
};
|
|
1854
|
+
rows.set(id, row);
|
|
1855
|
+
return row;
|
|
1856
|
+
}
|
|
1857
|
+
function emptyLane(id) {
|
|
1858
|
+
return {
|
|
1859
|
+
id,
|
|
1860
|
+
jobCount: 0,
|
|
1861
|
+
completedCount: 0,
|
|
1862
|
+
failedCount: 0,
|
|
1863
|
+
runningCount: 0,
|
|
1864
|
+
blockedCount: 0,
|
|
1865
|
+
needsCoordinatorReviewCount: 0,
|
|
1866
|
+
staleCount: 0,
|
|
1867
|
+
evidenceCount: 0,
|
|
1868
|
+
eventCount: 0
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
function attentionSummary(jobs) {
|
|
1872
|
+
return jobs.reduce((summary, job) => ({
|
|
1873
|
+
failedCount: summary.failedCount + (isFailedJob(job) ? 1 : 0),
|
|
1874
|
+
staleCount: summary.staleCount + (isStaleJob(job) ? 1 : 0),
|
|
1875
|
+
needsCoordinatorReviewCount: summary.needsCoordinatorReviewCount + (isNeedsCoordinatorReviewJob(job) ? 1 : 0),
|
|
1876
|
+
blockedCount: summary.blockedCount + (isBlockedJob(job) ? 1 : 0),
|
|
1877
|
+
evidenceCount: summary.evidenceCount + numberValue(job.evidencePathCount)
|
|
1878
|
+
}), { failedCount: 0, staleCount: 0, needsCoordinatorReviewCount: 0, blockedCount: 0, evidenceCount: 0 });
|
|
1879
|
+
}
|
|
1880
|
+
function successSummary(dashboard, jobs) {
|
|
1881
|
+
const semantic = semanticMetrics(dashboard.semantic);
|
|
1882
|
+
const replay = recordValue(recordValue(dashboard.semantic).replay);
|
|
1883
|
+
const ledger = dashboardApplyLedgerSummary(dashboard);
|
|
1884
|
+
return {
|
|
1885
|
+
completedCount: jobs.filter(isCompletedJob).length,
|
|
1886
|
+
readyCount: jobs.filter(isReadyJob).length,
|
|
1887
|
+
cleanSourceCount: jobs.filter((job) => sourceOwnershipViolations(job).length === 0 && numberValue(job.quarantinedChangedPathCount) === 0).length,
|
|
1888
|
+
evidenceCompleteCount: jobs.filter((job) => numberValue(job.evidencePathCount) > 0).length,
|
|
1889
|
+
appliedCount: ledger.applied,
|
|
1890
|
+
applyTotalCount: ledger.total,
|
|
1891
|
+
applyFailedCount: ledger.failed,
|
|
1892
|
+
landedCount: ledger.landed,
|
|
1893
|
+
semanticAcceptedClean: semantic.acceptedClean,
|
|
1894
|
+
semanticAlreadyApplied: numberValue(replay.alreadyAppliedCount)
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
function dashboardApplyLedgerSummary(dashboard) {
|
|
1898
|
+
const summary = recordValue(dashboard.summary);
|
|
1899
|
+
const summaryLedger = recordValue(summary.applyLedger);
|
|
1900
|
+
const raw = recordValue(dashboard.raw);
|
|
1901
|
+
const collection = recordValue(raw.collection);
|
|
1902
|
+
const collectionSummary = recordValue(collection.summary);
|
|
1903
|
+
const collectionLedger = recordValue(collectionSummary.applyLedger);
|
|
1904
|
+
const compact = recordValue(raw.compactDashboard);
|
|
1905
|
+
const compactLedger = recordValue(compact.applyLedger);
|
|
1906
|
+
const rawLedger = recordValue(raw.applyLedger);
|
|
1907
|
+
const rawLedgerSummary = recordValue(rawLedger.summary);
|
|
1908
|
+
const ledgers = [summaryLedger, collectionLedger, compactLedger, rawLedger, rawLedgerSummary];
|
|
1909
|
+
const landedIds = stringArray(summary.landedJobIds).length
|
|
1910
|
+
|| stringArray(collectionSummary.landedJobIds).length
|
|
1911
|
+
|| stringArray(compact.landedJobIds).length;
|
|
1912
|
+
const applied = firstLedgerCount(ledgers, 'applied');
|
|
1913
|
+
const committed = firstLedgerCount(ledgers, 'committed');
|
|
1914
|
+
const failed = firstLedgerCount(ledgers, 'failed');
|
|
1915
|
+
const skipped = firstLedgerCount(ledgers, 'skipped');
|
|
1916
|
+
const landed = numberValue(summary.landed)
|
|
1917
|
+
|| numberValue(summary.landedCount)
|
|
1918
|
+
|| numberValue(summary.applyLedgerLandedCount)
|
|
1919
|
+
|| numberValue(summaryLedger.landed)
|
|
1920
|
+
|| numberValue(collectionSummary.landed)
|
|
1921
|
+
|| numberValue(collectionSummary.landedCount)
|
|
1922
|
+
|| numberValue(collectionLedger.landed)
|
|
1923
|
+
|| numberValue(compact.landedCount)
|
|
1924
|
+
|| numberValue(compactLedger.landed)
|
|
1925
|
+
|| landedIds;
|
|
1926
|
+
const appliedCount = applied || landed;
|
|
1927
|
+
return {
|
|
1928
|
+
total: firstLedgerCount(ledgers, 'total') || appliedCount + committed + failed + skipped || landed,
|
|
1929
|
+
applied: appliedCount,
|
|
1930
|
+
committed,
|
|
1931
|
+
skipped,
|
|
1932
|
+
failed,
|
|
1933
|
+
landed
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
function firstLedgerCount(ledgers, key) {
|
|
1937
|
+
for (const ledger of ledgers) {
|
|
1938
|
+
const value = numberValue(ledger[key]);
|
|
1939
|
+
if (value > 0)
|
|
1940
|
+
return value;
|
|
1941
|
+
}
|
|
1942
|
+
return 0;
|
|
1943
|
+
}
|
|
1944
|
+
function tokenTimeSummary(dashboard, jobs) {
|
|
1945
|
+
const timeSummary = recordValue(dashboard.timeSeries?.summary);
|
|
1946
|
+
const context = contextPressureSummary(jobs);
|
|
1947
|
+
const actualInputTokens = numberValue(timeSummary.actualInputTokens) || context.actualInputTokens;
|
|
1948
|
+
const estimatedInputTokens = numberValue(timeSummary.estimatedInputTokens) || context.estimatedInputTokens;
|
|
1949
|
+
const uncachedInputTokens = numberValue(timeSummary.uncachedInputTokens) || context.uncachedInputTokens;
|
|
1950
|
+
const durationMs = numberValue(timeSummary.durationMs) || sumJobNumber(jobs, 'durationMs');
|
|
1951
|
+
const pointCount = numberValue(timeSummary.pointCount);
|
|
1952
|
+
const missingTimestampCount = numberValue(timeSummary.missingTimestampJobCount);
|
|
1953
|
+
const warningCount = context.warningCount + context.failedCount;
|
|
1954
|
+
const tokenValue = actualInputTokens ? formatNumber(actualInputTokens) : estimatedInputTokens ? formatNumber(estimatedInputTokens) : '-';
|
|
1955
|
+
const tokenDetail = actualInputTokens
|
|
1956
|
+
? `${formatNumber(uncachedInputTokens)} uncached · ${formatNumber(estimatedInputTokens)} est`
|
|
1957
|
+
: estimatedInputTokens
|
|
1958
|
+
? 'estimated input tokens'
|
|
1959
|
+
: 'no token data';
|
|
1960
|
+
const durationDetail = pointCount
|
|
1961
|
+
? `${formatNumber(pointCount)} buckets · ${formatNumber(missingTimestampCount)} missing time`
|
|
1962
|
+
: durationMs
|
|
1963
|
+
? 'summed worker duration'
|
|
1964
|
+
: 'no timing data';
|
|
1965
|
+
return {
|
|
1966
|
+
tokenValue,
|
|
1967
|
+
tokenDetail,
|
|
1968
|
+
durationValue: durationMs ? formatDuration(durationMs) : '-',
|
|
1969
|
+
durationDetail,
|
|
1970
|
+
tokenTone: warningCount ? 'warn' : 'neutral',
|
|
1971
|
+
timeTone: missingTimestampCount ? 'warn' : 'neutral',
|
|
1972
|
+
warningCount,
|
|
1973
|
+
missingTimestampCount
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1976
|
+
function warningPressureSummary(attention, audit, health, telemetry) {
|
|
1977
|
+
const failedBlocked = health.failedJobCount + health.blockedJobCount;
|
|
1978
|
+
const coordinatorStale = attention.needsCoordinatorReviewCount + attention.staleCount;
|
|
1979
|
+
const sourceViolations = audit.sourceOwnershipViolationCount;
|
|
1980
|
+
const generatedNoise = generatedNoiseCount(audit) + audit.quarantinedChangedPathCount;
|
|
1981
|
+
const budget = Math.max(health.contextWarningJobCount, telemetry.warningCount);
|
|
1982
|
+
const missingTime = telemetry.missingTimestampCount;
|
|
1983
|
+
const severe = failedBlocked + sourceViolations;
|
|
1984
|
+
const rows = [
|
|
1985
|
+
{ label: 'Failed / blocked', value: failedBlocked, detail: `${text(health.failedJobCount)} failed · ${text(health.blockedJobCount)} blocked`, tone: failedBlocked ? 'bad' : 'neutral' },
|
|
1986
|
+
{ label: 'Coordinator / stale', value: coordinatorStale, detail: `${text(attention.needsCoordinatorReviewCount)} coordinator · ${text(attention.staleCount)} stale`, tone: coordinatorStale ? 'warn' : 'neutral' },
|
|
1987
|
+
{ label: 'Context budget', value: budget, detail: `${text(health.contextWarningJobCount)} context · ${text(telemetry.warningCount)} token`, tone: budget ? 'warn' : 'neutral' },
|
|
1988
|
+
{ label: 'Source violations', value: sourceViolations, detail: 'outside owned source globs', tone: sourceViolations ? 'bad' : 'neutral' },
|
|
1989
|
+
{ label: 'Generated / quarantined', value: generatedNoise, detail: `${text(generatedNoiseCount(audit))} ignored · ${text(audit.quarantinedChangedPathCount)} quarantined`, tone: generatedNoise ? 'warn' : 'neutral' },
|
|
1990
|
+
{ label: 'Missing timing', value: missingTime, detail: 'jobs without timestamps', tone: missingTime ? 'warn' : 'neutral' }
|
|
1991
|
+
];
|
|
1992
|
+
const total = rows.reduce((sum, row) => sum + row.value, 0);
|
|
1993
|
+
return {
|
|
1994
|
+
total,
|
|
1995
|
+
severe,
|
|
1996
|
+
budget,
|
|
1997
|
+
tone: severe ? 'bad' : total ? 'warn' : 'neutral',
|
|
1998
|
+
headline: total ? formatNumber(total) : 'clear',
|
|
1999
|
+
detail: total ? `${formatNumber(severe)} blockers · ${formatNumber(budget)} budget` : 'no warning pressure',
|
|
2000
|
+
rows
|
|
2001
|
+
};
|
|
2002
|
+
}
|
|
2003
|
+
function sourceRows(dashboard) {
|
|
2004
|
+
const entries = [];
|
|
2005
|
+
if (dashboard.cwd)
|
|
2006
|
+
entries.push({ label: 'cwd', value: dashboard.cwd });
|
|
2007
|
+
if (dashboard.generatedAt)
|
|
2008
|
+
entries.push({ label: 'snapshot', value: formatTime(dashboard.generatedAt) });
|
|
2009
|
+
for (const [key, value] of Object.entries(dashboard.sources ?? {})) {
|
|
2010
|
+
const text = textValue(value, '');
|
|
2011
|
+
if (text)
|
|
2012
|
+
entries.push({ label: sourceLabel(key), value: text });
|
|
2013
|
+
}
|
|
2014
|
+
return entries;
|
|
2015
|
+
}
|
|
2016
|
+
function dashboardSourceLabel(dashboard) {
|
|
2017
|
+
const kind = dashboardSourceKind(dashboard);
|
|
2018
|
+
const generated = dashboard.generatedAt ? ` · snapshot ${formatTime(dashboard.generatedAt)}` : '';
|
|
2019
|
+
const activeCount = activeAgentTaskCount(dashboard, dashboard.jobs);
|
|
2020
|
+
const sources = recordValue(dashboard.sources);
|
|
2021
|
+
if (kind === 'demo')
|
|
2022
|
+
return `Demo fixture data · example agents, not a live run${generated}`;
|
|
2023
|
+
if (kind === 'lifetime')
|
|
2024
|
+
return `Workspace lifetime · ${text(numberValue(sources.loadedSourceCount) || numberValue(sources.sourceCount))} sources · ${text(activeCount)} active ${activeCount === 1 ? 'agent' : 'agents'}${generated}`;
|
|
2025
|
+
if (kind === 'live')
|
|
2026
|
+
return `Live run snapshot · ${text(activeCount)} active ${activeCount === 1 ? 'agent' : 'agents'}${generated}`;
|
|
2027
|
+
if (kind === 'stopped')
|
|
2028
|
+
return `Run snapshot · no active agents${generated}`;
|
|
2029
|
+
return `Snapshot${generated}`;
|
|
2030
|
+
}
|
|
2031
|
+
function dashboardSourceKind(dashboard) {
|
|
2032
|
+
const sources = recordValue(dashboard.sources);
|
|
2033
|
+
const sourceText = Object.values(sources).map((value) => textValue(value, '')).join(' ');
|
|
2034
|
+
const cwd = textValue(dashboard.cwd, '');
|
|
2035
|
+
const source = `${cwd} ${sourceText}`;
|
|
2036
|
+
if (/loom-ui-demo-populated|demo-populated|fixture/i.test(source))
|
|
2037
|
+
return 'demo';
|
|
2038
|
+
if (textValue(dashboard.kind, '') === 'frontier.loom-ui.lifetime-dashboard' || textValue(sources.lifetimeRoot, ''))
|
|
2039
|
+
return 'lifetime';
|
|
2040
|
+
if (/agent-runs|activeRun|pids\.json/i.test(source))
|
|
2041
|
+
return activeAgentTaskCount(dashboard, dashboard.jobs) > 0 ? 'live' : 'stopped';
|
|
2042
|
+
return 'snapshot';
|
|
2043
|
+
}
|
|
2044
|
+
function dashboardSourceSmokeMarker(kind) {
|
|
2045
|
+
if (kind === 'demo')
|
|
2046
|
+
return 'demo-source-strip';
|
|
2047
|
+
if (kind === 'lifetime')
|
|
2048
|
+
return 'lifetime-source-strip';
|
|
2049
|
+
if (kind === 'live')
|
|
2050
|
+
return 'live-source-strip';
|
|
2051
|
+
if (kind === 'stopped')
|
|
2052
|
+
return 'stopped-source-strip';
|
|
2053
|
+
return 'snapshot-source-strip';
|
|
2054
|
+
}
|
|
2055
|
+
function riskStatusLabel(dashboard, attention, loaded) {
|
|
2056
|
+
if (attention.failedCount || attention.staleCount || attention.needsCoordinatorReviewCount || attention.blockedCount)
|
|
2057
|
+
return 'Attention needed';
|
|
2058
|
+
if (!loaded)
|
|
2059
|
+
return 'No run loaded';
|
|
2060
|
+
return dashboard.ok ? 'Admission clean' : 'Run degraded';
|
|
2061
|
+
}
|
|
2062
|
+
function jobRisk(job) {
|
|
2063
|
+
if (isBlockedJob(job))
|
|
2064
|
+
return 'failed';
|
|
2065
|
+
if (isNeedsCoordinatorPortJob(job))
|
|
2066
|
+
return 'review';
|
|
2067
|
+
if (isFailedJob(job))
|
|
2068
|
+
return 'failed';
|
|
2069
|
+
if (isStaleJob(job))
|
|
2070
|
+
return 'stale';
|
|
2071
|
+
if (normalized(job.status) === 'running')
|
|
2072
|
+
return 'running';
|
|
2073
|
+
if (normalized(job.bucket) === 'ready-to-apply')
|
|
2074
|
+
return 'ready';
|
|
2075
|
+
if (normalized(job.status) === 'completed')
|
|
2076
|
+
return 'done';
|
|
2077
|
+
return 'unknown';
|
|
2078
|
+
}
|
|
2079
|
+
function isCompletedJob(job) {
|
|
2080
|
+
return normalized(job.status) === 'completed';
|
|
2081
|
+
}
|
|
2082
|
+
function isReadyJob(job) {
|
|
2083
|
+
return normalized(job.bucket) === 'ready-to-apply'
|
|
2084
|
+
|| normalized(job.disposition) === 'ready-to-apply'
|
|
2085
|
+
|| normalized(job.mergeReadiness).includes('ready');
|
|
2086
|
+
}
|
|
2087
|
+
function jobRiskLabel(job) {
|
|
2088
|
+
const risk = jobRisk(job);
|
|
2089
|
+
if (isBlockedJob(job))
|
|
2090
|
+
return 'blocked';
|
|
2091
|
+
if (risk === 'failed')
|
|
2092
|
+
return 'review';
|
|
2093
|
+
if (risk === 'stale')
|
|
2094
|
+
return 'stale';
|
|
2095
|
+
if (risk === 'review')
|
|
2096
|
+
return 'review';
|
|
2097
|
+
if (risk === 'ready')
|
|
2098
|
+
return 'ready';
|
|
2099
|
+
if (risk === 'done')
|
|
2100
|
+
return 'done';
|
|
2101
|
+
return text(job.status);
|
|
2102
|
+
}
|
|
2103
|
+
function isFailedJob(job) {
|
|
2104
|
+
const status = normalized(job.status);
|
|
2105
|
+
const bucket = normalized(job.bucket);
|
|
2106
|
+
const disposition = normalized(job.disposition);
|
|
2107
|
+
const readiness = normalized(job.mergeReadiness);
|
|
2108
|
+
return status === 'failed'
|
|
2109
|
+
|| bucket === 'failed-evidence'
|
|
2110
|
+
|| disposition === 'rejected'
|
|
2111
|
+
|| disposition === 'failed'
|
|
2112
|
+
|| readiness.includes('failed');
|
|
2113
|
+
}
|
|
2114
|
+
function isBlockedJob(job) {
|
|
2115
|
+
const status = normalized(job.status);
|
|
2116
|
+
const bucket = normalized(job.bucket);
|
|
2117
|
+
const disposition = normalized(job.disposition);
|
|
2118
|
+
const readiness = normalized(job.mergeReadiness);
|
|
2119
|
+
return status === 'blocked'
|
|
2120
|
+
|| bucket === 'blocked'
|
|
2121
|
+
|| bucket === 'waiting-on-human'
|
|
2122
|
+
|| bucket === 'waiting-on-dependency'
|
|
2123
|
+
|| disposition === 'blocked'
|
|
2124
|
+
|| readiness === 'blocked';
|
|
2125
|
+
}
|
|
2126
|
+
function isNeedsCoordinatorReviewJob(job) {
|
|
2127
|
+
return isNeedsCoordinatorPortJob(job);
|
|
2128
|
+
}
|
|
2129
|
+
function isNeedsCoordinatorPortJob(job) {
|
|
2130
|
+
const bucket = normalized(job.bucket);
|
|
2131
|
+
const disposition = normalized(job.disposition);
|
|
2132
|
+
const readiness = normalized(job.mergeReadiness);
|
|
2133
|
+
return bucket === 'needs-human-port'
|
|
2134
|
+
|| bucket === 'needs-human-review'
|
|
2135
|
+
|| bucket === 'needs-human-decision'
|
|
2136
|
+
|| bucket === 'needs-coordinator-port'
|
|
2137
|
+
|| bucket === 'needs-coordinator-review'
|
|
2138
|
+
|| bucket === 'needs-coordinator-decision'
|
|
2139
|
+
|| disposition.includes('needs')
|
|
2140
|
+
|| readiness.includes('needs')
|
|
2141
|
+
|| normalized(job.status) === 'needs-review';
|
|
2142
|
+
}
|
|
2143
|
+
function isCoordinatorReviewJob(job) {
|
|
2144
|
+
if (isResolvedCoordinatorReviewJob(job))
|
|
2145
|
+
return false;
|
|
2146
|
+
return isNeedsCoordinatorPortJob(job)
|
|
2147
|
+
|| isFailedJob(job)
|
|
2148
|
+
|| isStaleJob(job)
|
|
2149
|
+
|| normalized(job.disposition).includes('review')
|
|
2150
|
+
|| normalized(job.mergeReadiness).includes('review');
|
|
2151
|
+
}
|
|
2152
|
+
function isStaleJob(job) {
|
|
2153
|
+
return normalized(job.bucket).includes('stale')
|
|
2154
|
+
|| normalized(job.disposition).includes('stale')
|
|
2155
|
+
|| normalized(job.mergeReadiness).includes('stale');
|
|
2156
|
+
}
|
|
2157
|
+
function isTerminalJob(job) {
|
|
2158
|
+
const status = normalized(job.status);
|
|
2159
|
+
return status === 'completed'
|
|
2160
|
+
|| status === 'failed'
|
|
2161
|
+
|| status === 'blocked'
|
|
2162
|
+
|| Boolean(textValue(job.bucket, ''));
|
|
2163
|
+
}
|
|
2164
|
+
function laneOf(job) {
|
|
2165
|
+
return textValue(job.lane, 'unassigned');
|
|
2166
|
+
}
|
|
2167
|
+
function reasonText(value) {
|
|
2168
|
+
if (Array.isArray(value) && value.length)
|
|
2169
|
+
return value.slice(0, 3).map(String).join(', ');
|
|
2170
|
+
return '-';
|
|
2171
|
+
}
|
|
2172
|
+
function jobReasonText(job) {
|
|
2173
|
+
const values = [...stringArray(job.collectReasonClasses), ...stringArray(job.reasons)];
|
|
2174
|
+
return reasonText(values);
|
|
2175
|
+
}
|
|
2176
|
+
function pathSummary(job) {
|
|
2177
|
+
const ownershipViolationCount = numberValue(job.ownershipViolationCount);
|
|
2178
|
+
const sourceOwnership = sourceOwnershipViolations(job);
|
|
2179
|
+
const ignoredOwnership = ignoredOwnershipViolations(job);
|
|
2180
|
+
const ignoredChangedPathCount = numberValue(job.ignoredChangedPathCount);
|
|
2181
|
+
const quarantinedChangedPathCount = numberValue(job.quarantinedChangedPathCount);
|
|
2182
|
+
const quarantinedChangedPaths = stringArray(job.quarantinedChangedPaths);
|
|
2183
|
+
const ignoredCount = ignoredOwnership.length + ignoredChangedPathCount;
|
|
2184
|
+
const issueText = sourceOwnership.length
|
|
2185
|
+
? `${text(sourceOwnership.length)} source: ${pathList(sourceOwnership)}`
|
|
2186
|
+
: ignoredOwnership.length
|
|
2187
|
+
? `${text(ignoredCount)} generated noise: ${pathList(ignoredOwnership)}`
|
|
2188
|
+
: quarantinedChangedPaths.length
|
|
2189
|
+
? `${text(quarantinedChangedPathCount)} quarantined: ${pathList(quarantinedChangedPaths)}`
|
|
2190
|
+
: ownershipViolationCount || ignoredCount || quarantinedChangedPathCount
|
|
2191
|
+
? `${text(sourceOwnership.length)} source · ${text(ignoredCount)} generated · ${text(quarantinedChangedPathCount)} quarantined`
|
|
2192
|
+
: 'clean';
|
|
2193
|
+
return _jsxs("span", { className: "path-summary", children: [_jsx("b", { children: text(job.changedPathCount) }), _jsx("small", { children: issueText })] });
|
|
2194
|
+
}
|
|
2195
|
+
function pathSummaryText(job) {
|
|
2196
|
+
const sourceOwnership = sourceOwnershipViolations(job);
|
|
2197
|
+
const ignoredOwnership = ignoredOwnershipViolations(job);
|
|
2198
|
+
const ignoredChangedPathCount = numberValue(job.ignoredChangedPathCount);
|
|
2199
|
+
const quarantinedChangedPathCount = numberValue(job.quarantinedChangedPathCount);
|
|
2200
|
+
const ignoredCount = ignoredOwnership.length + ignoredChangedPathCount;
|
|
2201
|
+
if (sourceOwnership.length)
|
|
2202
|
+
return `${text(sourceOwnership.length)} source issue`;
|
|
2203
|
+
if (quarantinedChangedPathCount)
|
|
2204
|
+
return `${text(quarantinedChangedPathCount)} quarantined`;
|
|
2205
|
+
if (ignoredCount)
|
|
2206
|
+
return `${text(ignoredCount)} generated noise`;
|
|
2207
|
+
return `${text(job.changedPathCount)} changed paths`;
|
|
2208
|
+
}
|
|
2209
|
+
function sourceOwnershipViolations(job) {
|
|
2210
|
+
const explicit = stringArray(job.sourceOwnershipViolations);
|
|
2211
|
+
if (explicit.length)
|
|
2212
|
+
return explicit.filter((path) => !isGeneratedPath(path));
|
|
2213
|
+
return stringArray(job.ownershipViolations).filter((path) => !isGeneratedPath(path));
|
|
2214
|
+
}
|
|
2215
|
+
function ignoredOwnershipViolations(job) {
|
|
2216
|
+
const explicit = stringArray(job.ignoredOwnershipViolations);
|
|
2217
|
+
return uniqueStrings([...explicit, ...stringArray(job.sourceOwnershipViolations).filter(isGeneratedPath), ...stringArray(job.ownershipViolations).filter(isGeneratedPath)]);
|
|
2218
|
+
}
|
|
2219
|
+
function uniqueStrings(values) {
|
|
2220
|
+
return Array.from(new Set(values.filter(Boolean)));
|
|
2221
|
+
}
|
|
2222
|
+
function isGeneratedPath(value) {
|
|
2223
|
+
const path = value.replace(/\\/g, '/');
|
|
2224
|
+
return path.includes('/.cache/')
|
|
2225
|
+
|| path.startsWith('.cache/')
|
|
2226
|
+
|| path.endsWith('.tsbuildinfo')
|
|
2227
|
+
|| path.includes('/dist/')
|
|
2228
|
+
|| path.startsWith('dist/')
|
|
2229
|
+
|| path.includes('/node_modules/')
|
|
2230
|
+
|| path.startsWith('node_modules/');
|
|
2231
|
+
}
|
|
2232
|
+
function pathList(paths) {
|
|
2233
|
+
const visible = paths.slice(0, 2);
|
|
2234
|
+
const more = paths.length > visible.length ? ` +${paths.length - visible.length}` : '';
|
|
2235
|
+
return `${visible.join(', ')}${more}`;
|
|
2236
|
+
}
|
|
2237
|
+
function contributionGrid(dashboard, jobs, events) {
|
|
2238
|
+
const anchor = Math.max(Date.now(), timeValue(dashboard.generatedAt) ?? 0, ...events.map((event) => timeValue(event.at) ?? 0), ...jobs.map(jobCompletionTime));
|
|
2239
|
+
const year = new Date(anchor).getFullYear();
|
|
2240
|
+
const yearStart = new Date(year, 0, 1).getTime();
|
|
2241
|
+
const yearEnd = new Date(year, 11, 31).getTime();
|
|
2242
|
+
const gridStart = addLocalDays(yearStart, -new Date(yearStart).getDay());
|
|
2243
|
+
const gridEnd = addLocalDays(yearEnd, 6 - new Date(yearEnd).getDay());
|
|
2244
|
+
const dayStarts = localDayRange(gridStart, gridEnd);
|
|
2245
|
+
const weekCount = Math.ceil(dayStarts.length / 7);
|
|
2246
|
+
const rows = new Map();
|
|
2247
|
+
for (const at of dayStarts) {
|
|
2248
|
+
const date = dateKey(at);
|
|
2249
|
+
const inYear = at >= yearStart && at <= yearEnd;
|
|
2250
|
+
rows.set(date, {
|
|
2251
|
+
date,
|
|
2252
|
+
label: formatDay(at),
|
|
2253
|
+
count: 0,
|
|
2254
|
+
completed: 0,
|
|
2255
|
+
events: 0,
|
|
2256
|
+
level: 0,
|
|
2257
|
+
inYear
|
|
2258
|
+
});
|
|
2259
|
+
}
|
|
2260
|
+
for (const event of events) {
|
|
2261
|
+
const at = timeValue(event.at);
|
|
2262
|
+
if (at === undefined)
|
|
2263
|
+
continue;
|
|
2264
|
+
const row = rows.get(dateKey(at));
|
|
2265
|
+
if (!row)
|
|
2266
|
+
continue;
|
|
2267
|
+
row.events += 1;
|
|
2268
|
+
if (isProgressEvent(event))
|
|
2269
|
+
row.completed += 1;
|
|
2270
|
+
}
|
|
2271
|
+
for (const job of jobs) {
|
|
2272
|
+
if (!isPositiveProgressJob(job))
|
|
2273
|
+
continue;
|
|
2274
|
+
const at = jobCompletionTime(job) || timeValue(dashboard.generatedAt);
|
|
2275
|
+
if (at === undefined)
|
|
2276
|
+
continue;
|
|
2277
|
+
const row = rows.get(dateKey(at));
|
|
2278
|
+
if (!row)
|
|
2279
|
+
continue;
|
|
2280
|
+
row.completed += 1;
|
|
2281
|
+
}
|
|
2282
|
+
const days = Array.from(rows.values()).map((row) => ({
|
|
2283
|
+
...row,
|
|
2284
|
+
count: row.completed + row.events
|
|
2285
|
+
}));
|
|
2286
|
+
const yearDays = days.filter((day) => day.inYear);
|
|
2287
|
+
const maxCount = Math.max(0, ...yearDays.map((day) => day.count));
|
|
2288
|
+
const levelled = days.map((day) => ({
|
|
2289
|
+
...day,
|
|
2290
|
+
level: contributionLevel(day.count, maxCount)
|
|
2291
|
+
}));
|
|
2292
|
+
const weeks = Array.from({ length: weekCount }, (_, index) => levelled.slice(index * 7, index * 7 + 7));
|
|
2293
|
+
return {
|
|
2294
|
+
year,
|
|
2295
|
+
days: levelled.filter((day) => day.inYear),
|
|
2296
|
+
weeks,
|
|
2297
|
+
maxCount,
|
|
2298
|
+
totalDone: yearDays.reduce((sum, day) => sum + day.completed, 0),
|
|
2299
|
+
totalEvents: yearDays.reduce((sum, day) => sum + day.events, 0),
|
|
2300
|
+
activeDays: yearDays.filter((day) => day.count > 0).length
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
2304
|
+
function startOfLocalDay(value) {
|
|
2305
|
+
const date = new Date(value);
|
|
2306
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
|
|
2307
|
+
}
|
|
2308
|
+
function addLocalDays(value, days) {
|
|
2309
|
+
const date = new Date(value);
|
|
2310
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + days).getTime();
|
|
2311
|
+
}
|
|
2312
|
+
function localDayRange(start, end) {
|
|
2313
|
+
const out = [];
|
|
2314
|
+
for (let at = startOfLocalDay(start); at <= end; at = addLocalDays(at, 1)) {
|
|
2315
|
+
out.push(at);
|
|
2316
|
+
}
|
|
2317
|
+
return out;
|
|
2318
|
+
}
|
|
2319
|
+
function dateKey(value) {
|
|
2320
|
+
const date = new Date(startOfLocalDay(value));
|
|
2321
|
+
const year = date.getFullYear();
|
|
2322
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
2323
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
2324
|
+
return `${year}-${month}-${day}`;
|
|
2325
|
+
}
|
|
2326
|
+
function formatDay(value) {
|
|
2327
|
+
return new Intl.DateTimeFormat('en-US', {
|
|
2328
|
+
month: 'short',
|
|
2329
|
+
day: 'numeric',
|
|
2330
|
+
year: 'numeric'
|
|
2331
|
+
}).format(new Date(value));
|
|
2332
|
+
}
|
|
2333
|
+
function contributionLevel(count, maxCount) {
|
|
2334
|
+
if (!count)
|
|
2335
|
+
return 0;
|
|
2336
|
+
if (maxCount <= 1)
|
|
2337
|
+
return 1;
|
|
2338
|
+
return Math.min(4, Math.max(1, Math.ceil((count / maxCount) * 4)));
|
|
2339
|
+
}
|
|
2340
|
+
function contributionMonthLabel(week, index, weeks) {
|
|
2341
|
+
const firstInYear = week.find((day) => day.inYear);
|
|
2342
|
+
if (!firstInYear)
|
|
2343
|
+
return '';
|
|
2344
|
+
const startsMonth = week.find((day) => day.inYear && day.date.endsWith('-01'));
|
|
2345
|
+
if (index > 0 && !startsMonth)
|
|
2346
|
+
return '';
|
|
2347
|
+
const day = startsMonth ?? firstInYear;
|
|
2348
|
+
const previousMonth = weeks[index - 1]?.find((entry) => entry.inYear)?.date.slice(5, 7);
|
|
2349
|
+
const month = day.date.slice(5, 7);
|
|
2350
|
+
if (!startsMonth && previousMonth === month)
|
|
2351
|
+
return '';
|
|
2352
|
+
return new Intl.DateTimeFormat('en-US', { month: 'short' }).format(new Date(`${day.date}T00:00:00`));
|
|
2353
|
+
}
|
|
2354
|
+
function jobCompletionTime(job) {
|
|
2355
|
+
return timeValue(job.finishedAt)
|
|
2356
|
+
?? timeValue(job.completedAt)
|
|
2357
|
+
?? timeValue(job.endedAt)
|
|
2358
|
+
?? timeValue(job.updatedAt)
|
|
2359
|
+
?? 0;
|
|
2360
|
+
}
|
|
2361
|
+
function isPositiveProgressJob(job) {
|
|
2362
|
+
return isCompletedJob(job)
|
|
2363
|
+
|| isReadyJob(job)
|
|
2364
|
+
|| normalized(job.bucket) === 'applied'
|
|
2365
|
+
|| normalized(job.bucket) === 'landed'
|
|
2366
|
+
|| normalized(job.disposition) === 'applied'
|
|
2367
|
+
|| normalized(job.disposition) === 'landed';
|
|
2368
|
+
}
|
|
2369
|
+
function timelineSummary(dashboard, jobs, events) {
|
|
2370
|
+
const totalJobs = jobs.length || numberValue(dashboard.summary.jobCount);
|
|
2371
|
+
const terminalJobs = jobs.filter(isTerminalJob).length;
|
|
2372
|
+
const runningJobs = jobs.filter((job) => normalized(job.status) === 'running').length;
|
|
2373
|
+
const attentionJobs = jobs.filter((job) => isFailedJob(job) || isNeedsCoordinatorReviewJob(job) || isStaleJob(job) || isBlockedJob(job)).length;
|
|
2374
|
+
const progressPercent = totalJobs ? percentNumber(terminalJobs / totalJobs) : 0;
|
|
2375
|
+
const timestamped = events
|
|
2376
|
+
.map((event) => ({ event, at: timeValue(event.at) }))
|
|
2377
|
+
.filter((entry) => entry.at !== undefined)
|
|
2378
|
+
.sort((left, right) => left.at - right.at);
|
|
2379
|
+
const finished = new Set();
|
|
2380
|
+
const points = timestamped.map(({ event, at }) => {
|
|
2381
|
+
const type = text(event.type);
|
|
2382
|
+
const jobId = textValue(event.jobId, '');
|
|
2383
|
+
if (jobId && normalized(type) === 'agent.finished')
|
|
2384
|
+
finished.add(jobId);
|
|
2385
|
+
const pointProgress = normalized(type) === 'swarm.finished'
|
|
2386
|
+
? progressPercent
|
|
2387
|
+
: totalJobs
|
|
2388
|
+
? Math.min(progressPercent, percentNumber(finished.size / totalJobs))
|
|
2389
|
+
: 0;
|
|
2390
|
+
return {
|
|
2391
|
+
at,
|
|
2392
|
+
type,
|
|
2393
|
+
lane: text(event.lane),
|
|
2394
|
+
jobId: jobId || '-',
|
|
2395
|
+
message: text(event.message),
|
|
2396
|
+
progressPercent: pointProgress
|
|
2397
|
+
};
|
|
2398
|
+
});
|
|
2399
|
+
return {
|
|
2400
|
+
totalJobs,
|
|
2401
|
+
terminalJobs,
|
|
2402
|
+
runningJobs,
|
|
2403
|
+
attentionJobs,
|
|
2404
|
+
progressPercent,
|
|
2405
|
+
points,
|
|
2406
|
+
firstAt: points[0]?.at,
|
|
2407
|
+
lastAt: points[points.length - 1]?.at,
|
|
2408
|
+
exactTimingAvailable: jobs.some((job) => timeValue(job.startedAt) !== undefined && (timeValue(job.finishedAt) !== undefined || numberValue(job.durationMs) > 0)),
|
|
2409
|
+
eventWindowLimited: events.length >= 80
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
function timelineBottlenecks(jobs, audit) {
|
|
2413
|
+
return [
|
|
2414
|
+
{ label: 'Failed', value: jobs.filter(isFailedJob).length, detail: 'failed or rejected jobs', tone: 'bad' },
|
|
2415
|
+
{ label: 'Coordinator review', value: jobs.filter(isNeedsCoordinatorReviewJob).length, detail: 'outputs waiting on coordinator review', tone: 'warn' },
|
|
2416
|
+
{ label: 'Stale', value: jobs.filter(isStaleJob).length, detail: 'stale against current head', tone: 'warn' },
|
|
2417
|
+
{ label: 'Blocked', value: jobs.filter(isBlockedJob).length, detail: 'scheduler or dependency blocked', tone: 'bad' },
|
|
2418
|
+
{ label: 'Source violations', value: audit.sourceOwnershipViolationCount, detail: 'changed paths outside owned source globs', tone: 'bad' },
|
|
2419
|
+
{
|
|
2420
|
+
label: 'Truncated logs',
|
|
2421
|
+
value: jobs.filter((job) => numberValue(job.eventBytesTruncated) + numberValue(job.stderrBytesTruncated) > 0).length,
|
|
2422
|
+
detail: 'lost event or stderr detail',
|
|
2423
|
+
tone: 'warn'
|
|
2424
|
+
}
|
|
2425
|
+
];
|
|
2426
|
+
}
|
|
2427
|
+
function dashboardHealthSummary(dashboard, jobs, attention) {
|
|
2428
|
+
const summary = recordValue(dashboard.health?.summary);
|
|
2429
|
+
const jobCount = numberValue(summary.jobCount) || jobs.length;
|
|
2430
|
+
const failedJobCount = numberValue(summary.failedJobCount) || attention.failedCount;
|
|
2431
|
+
const blockedJobCount = numberValue(summary.blockedJobCount) || attention.blockedCount;
|
|
2432
|
+
const warningJobCount = numberValue(summary.warningJobCount) || attention.needsCoordinatorReviewCount + attention.staleCount;
|
|
2433
|
+
const terminalJobCount = numberValue(summary.terminalJobCount) || jobs.filter(isTerminalJob).length;
|
|
2434
|
+
const status = textValue(dashboard.health?.status, failedJobCount || blockedJobCount ? 'failed' : warningJobCount ? 'warning' : jobCount ? 'healthy' : 'unknown');
|
|
2435
|
+
return {
|
|
2436
|
+
status,
|
|
2437
|
+
tone: healthTone(status),
|
|
2438
|
+
jobCount,
|
|
2439
|
+
healthyJobCount: numberValue(summary.healthyJobCount) || Math.max(0, jobCount - failedJobCount - blockedJobCount - warningJobCount),
|
|
2440
|
+
warningJobCount,
|
|
2441
|
+
failedJobCount,
|
|
2442
|
+
blockedJobCount,
|
|
2443
|
+
runningJobCount: numberValue(summary.runningJobCount) || jobs.filter((job) => normalized(job.status) === 'running').length,
|
|
2444
|
+
terminalJobCount,
|
|
2445
|
+
readyToApplyJobCount: numberValue(summary.readyToApplyJobCount) || jobs.filter(isReadyJob).length,
|
|
2446
|
+
contextWarningJobCount: numberValue(summary.contextWarningJobCount) || jobs.filter(isContextBudgetWarningJob).length,
|
|
2447
|
+
semanticCleanJobCount: numberValue(summary.semanticCleanJobCount) || jobs.filter((job) => normalized(job.semanticReadiness) === 'clean').length,
|
|
2448
|
+
semanticCandidateJobCount: numberValue(summary.semanticCandidateJobCount) || jobs.filter((job) => normalized(job.semanticReadiness) === 'candidate').length,
|
|
2449
|
+
completionRatio: numberValue(summary.completionRatio) || (jobCount ? terminalJobCount / jobCount : 0),
|
|
2450
|
+
failureRatio: numberValue(summary.failureRatio) || (jobCount ? (failedJobCount + blockedJobCount) / jobCount : 0)
|
|
2451
|
+
};
|
|
2452
|
+
}
|
|
2453
|
+
function dashboardHealthRows(dashboard, jobs, attention) {
|
|
2454
|
+
const health = dashboardHealthSummary(dashboard, jobs, attention);
|
|
2455
|
+
return [
|
|
2456
|
+
{ label: 'Healthy', value: health.healthyJobCount, tone: 'good' },
|
|
2457
|
+
{ label: 'Warnings', value: health.warningJobCount, tone: health.warningJobCount ? 'warn' : 'neutral' },
|
|
2458
|
+
{ label: 'Failed', value: health.failedJobCount, tone: health.failedJobCount ? 'bad' : 'neutral' },
|
|
2459
|
+
{ label: 'Blocked', value: health.blockedJobCount, tone: health.blockedJobCount ? 'bad' : 'neutral' },
|
|
2460
|
+
{ label: 'Running', value: health.runningJobCount, tone: 'neutral' },
|
|
2461
|
+
{ label: 'Semantic clean', value: health.semanticCleanJobCount, tone: health.semanticCleanJobCount ? 'good' : 'neutral' }
|
|
2462
|
+
];
|
|
2463
|
+
}
|
|
2464
|
+
function healthTone(value) {
|
|
2465
|
+
const status = normalized(value);
|
|
2466
|
+
if (status === 'healthy')
|
|
2467
|
+
return 'good';
|
|
2468
|
+
if (status === 'warning' || status === 'running')
|
|
2469
|
+
return 'warn';
|
|
2470
|
+
if (status === 'failed' || status === 'blocked')
|
|
2471
|
+
return 'bad';
|
|
2472
|
+
return 'neutral';
|
|
2473
|
+
}
|
|
2474
|
+
function timelinePointLeft(point, summary) {
|
|
2475
|
+
const first = summary.firstAt ?? point.at;
|
|
2476
|
+
const last = summary.lastAt ?? point.at;
|
|
2477
|
+
if (last <= first)
|
|
2478
|
+
return 0;
|
|
2479
|
+
return Math.max(0, Math.min(100, Math.round(((point.at - first) / (last - first)) * 100)));
|
|
2480
|
+
}
|
|
2481
|
+
function timelinePointTone(point) {
|
|
2482
|
+
const type = normalized(point.type);
|
|
2483
|
+
const message = normalized(point.message);
|
|
2484
|
+
if (type.includes('failed') || message.includes('failed') || message.includes('blocked'))
|
|
2485
|
+
return 'bad';
|
|
2486
|
+
if (type.includes('adaptive') || message.includes('stale') || message.includes('human'))
|
|
2487
|
+
return 'warn';
|
|
2488
|
+
if (type.includes('finished'))
|
|
2489
|
+
return 'good';
|
|
2490
|
+
return 'neutral';
|
|
2491
|
+
}
|
|
2492
|
+
function tabMeta(tab, input) {
|
|
2493
|
+
void input.events;
|
|
2494
|
+
void input.sources;
|
|
2495
|
+
if (tab === 'work')
|
|
2496
|
+
return `${text(input.jobs)} tasks`;
|
|
2497
|
+
if (tab === 'board')
|
|
2498
|
+
return 'AI tasks';
|
|
2499
|
+
if (tab === 'swarm')
|
|
2500
|
+
return 'active agents';
|
|
2501
|
+
if (tab === 'performance')
|
|
2502
|
+
return 'cost trends';
|
|
2503
|
+
if (tab === 'history')
|
|
2504
|
+
return 'change graph';
|
|
2505
|
+
if (tab === 'testing')
|
|
2506
|
+
return 'quality checks';
|
|
2507
|
+
return `${text(input.questions)} open`;
|
|
2508
|
+
}
|
|
2509
|
+
function sourceLabel(value) {
|
|
2510
|
+
return value.replace(/([A-Z])/g, ' $1').replace(/^./, (first) => first.toUpperCase());
|
|
2511
|
+
}
|
|
2512
|
+
function offlineDashboard(error) {
|
|
2513
|
+
return {
|
|
2514
|
+
ok: false,
|
|
2515
|
+
generatedAt: Date.now(),
|
|
2516
|
+
summary: {
|
|
2517
|
+
jobCount: 0,
|
|
2518
|
+
completedCount: 0,
|
|
2519
|
+
failedCount: 0,
|
|
2520
|
+
runningCount: 0,
|
|
2521
|
+
blockedCount: 0,
|
|
2522
|
+
bucketCounts: { total: 0, 'ready-to-apply': 0 }
|
|
2523
|
+
},
|
|
2524
|
+
lanes: [],
|
|
2525
|
+
jobs: [],
|
|
2526
|
+
events: [],
|
|
2527
|
+
sources: { error: error instanceof Error ? error.message : String(error) }
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
function text(value) {
|
|
2531
|
+
return textValue(value, '-');
|
|
2532
|
+
}
|
|
2533
|
+
function textValue(value, fallback) {
|
|
2534
|
+
if (value === undefined || value === null || value === '')
|
|
2535
|
+
return fallback;
|
|
2536
|
+
return String(value);
|
|
2537
|
+
}
|
|
2538
|
+
function numberValue(value) {
|
|
2539
|
+
const number = Number(value ?? 0);
|
|
2540
|
+
return Number.isFinite(number) ? number : 0;
|
|
2541
|
+
}
|
|
2542
|
+
function timeValue(value) {
|
|
2543
|
+
if (value === undefined || value === null || value === '')
|
|
2544
|
+
return undefined;
|
|
2545
|
+
const number = Number(value);
|
|
2546
|
+
if (Number.isFinite(number) && number > 0)
|
|
2547
|
+
return number;
|
|
2548
|
+
if (typeof value === 'string') {
|
|
2549
|
+
const parsed = Date.parse(value);
|
|
2550
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
2551
|
+
return parsed;
|
|
2552
|
+
}
|
|
2553
|
+
return undefined;
|
|
2554
|
+
}
|
|
2555
|
+
function sumJobNumber(jobs, key) {
|
|
2556
|
+
return jobs.reduce((sum, job) => sum + numberValue(job[key]), 0);
|
|
2557
|
+
}
|
|
2558
|
+
function progressChartSeries(dashboard, jobs, events) {
|
|
2559
|
+
const timeSeries = timeSeriesChartSeries(dashboard);
|
|
2560
|
+
if (timeSeries.length)
|
|
2561
|
+
return timeSeries;
|
|
2562
|
+
const completed = jobs.filter((job) => normalized(job.status) === 'completed').length;
|
|
2563
|
+
const attention = jobs.filter((job) => isFailedJob(job) || isStaleJob(job) || isNeedsCoordinatorReviewJob(job)).length;
|
|
2564
|
+
const progressEvents = events.filter(isProgressEvent).length;
|
|
2565
|
+
const attentionEvents = events.filter(isAttentionEvent).length;
|
|
2566
|
+
return [
|
|
2567
|
+
{
|
|
2568
|
+
id: 'event-activity',
|
|
2569
|
+
title: 'Activity',
|
|
2570
|
+
value: formatNumber(events.length),
|
|
2571
|
+
detail: 'event cadence',
|
|
2572
|
+
points: eventBucketPoints(events, () => true)
|
|
2573
|
+
},
|
|
2574
|
+
{
|
|
2575
|
+
id: 'progress-events',
|
|
2576
|
+
title: 'Progress',
|
|
2577
|
+
value: progressEvents ? formatNumber(progressEvents) : `${formatNumber(completed)}/${formatNumber(jobs.length)}`,
|
|
2578
|
+
detail: progressEvents ? 'ready/completed events' : 'completed jobs',
|
|
2579
|
+
tone: 'good',
|
|
2580
|
+
points: eventBucketPoints(events, isProgressEvent, 'good')
|
|
2581
|
+
},
|
|
2582
|
+
{
|
|
2583
|
+
id: 'attention-events',
|
|
2584
|
+
title: 'Attention',
|
|
2585
|
+
value: attentionEvents ? formatNumber(attentionEvents) : formatNumber(attention),
|
|
2586
|
+
detail: attentionEvents ? 'risk events' : 'jobs with risk',
|
|
2587
|
+
tone: attention || attentionEvents ? 'bad' : 'neutral',
|
|
2588
|
+
points: eventBucketPoints(events, isAttentionEvent, attention || attentionEvents ? 'bad' : 'neutral')
|
|
2589
|
+
}
|
|
2590
|
+
];
|
|
2591
|
+
}
|
|
2592
|
+
function timeSeriesChartSeries(dashboard) {
|
|
2593
|
+
const points = timeSeriesPoints(dashboard);
|
|
2594
|
+
if (!points.length)
|
|
2595
|
+
return [];
|
|
2596
|
+
const summary = recordValue(dashboard.timeSeries?.summary);
|
|
2597
|
+
const terminal = numberValue(summary.terminalJobCount);
|
|
2598
|
+
const warnings = numberValue(summary.warningJobCount);
|
|
2599
|
+
const failures = numberValue(summary.failureJobCount) + numberValue(summary.blockedJobCount);
|
|
2600
|
+
const durationMs = numberValue(summary.durationMs);
|
|
2601
|
+
return [
|
|
2602
|
+
{
|
|
2603
|
+
id: 'time-series-terminal',
|
|
2604
|
+
title: 'Terminal jobs',
|
|
2605
|
+
value: formatNumber(terminal),
|
|
2606
|
+
detail: `${formatBucketSize(dashboard.timeSeries?.bucketMs)} buckets`,
|
|
2607
|
+
tone: 'good',
|
|
2608
|
+
points: points.map((point) => timeSeriesPoint(point, 'terminalJobCount', 'terminal jobs', 'good'))
|
|
2609
|
+
},
|
|
2610
|
+
{
|
|
2611
|
+
id: 'time-series-warnings',
|
|
2612
|
+
title: 'Warnings',
|
|
2613
|
+
value: formatNumber(warnings),
|
|
2614
|
+
detail: 'warning jobs by bucket',
|
|
2615
|
+
tone: warnings ? 'warn' : 'neutral',
|
|
2616
|
+
points: points.map((point) => timeSeriesPoint(point, 'warningJobCount', 'warnings', warnings ? 'warn' : 'neutral'))
|
|
2617
|
+
},
|
|
2618
|
+
{
|
|
2619
|
+
id: 'time-series-failures',
|
|
2620
|
+
title: 'Failures',
|
|
2621
|
+
value: formatNumber(failures),
|
|
2622
|
+
detail: 'failed and blocked jobs',
|
|
2623
|
+
tone: failures ? 'bad' : 'neutral',
|
|
2624
|
+
points: points.map((point) => ({
|
|
2625
|
+
...timeSeriesPoint(point, 'failureJobCount', 'failures', failures ? 'bad' : 'neutral'),
|
|
2626
|
+
value: numberValue(point.failureJobCount) + numberValue(point.blockedJobCount)
|
|
2627
|
+
}))
|
|
2628
|
+
},
|
|
2629
|
+
{
|
|
2630
|
+
id: 'time-series-duration',
|
|
2631
|
+
title: 'Duration',
|
|
2632
|
+
value: formatDuration(durationMs),
|
|
2633
|
+
detail: 'bucketed worker duration',
|
|
2634
|
+
points: points.map((point) => timeSeriesPoint(point, 'durationMs', 'duration', 'neutral', formatDuration))
|
|
2635
|
+
}
|
|
2636
|
+
];
|
|
2637
|
+
}
|
|
2638
|
+
function contextLoadChartSeries(dashboard, jobs) {
|
|
2639
|
+
const points = timeSeriesPoints(dashboard);
|
|
2640
|
+
if (points.length) {
|
|
2641
|
+
const summary = recordValue(dashboard.timeSeries?.summary);
|
|
2642
|
+
const actualInputTokens = numberValue(summary.actualInputTokens);
|
|
2643
|
+
return [
|
|
2644
|
+
{
|
|
2645
|
+
id: 'time-series-estimated-tokens',
|
|
2646
|
+
title: 'Estimated tokens',
|
|
2647
|
+
value: formatNumber(numberValue(summary.estimatedInputTokens)),
|
|
2648
|
+
detail: 'bucketed input estimate',
|
|
2649
|
+
points: points.map((point) => timeSeriesPoint(point, 'estimatedInputTokens', 'estimated tokens'))
|
|
2650
|
+
},
|
|
2651
|
+
{
|
|
2652
|
+
id: 'time-series-actual-tokens',
|
|
2653
|
+
title: 'Actual tokens',
|
|
2654
|
+
value: actualInputTokens ? formatNumber(actualInputTokens) : '-',
|
|
2655
|
+
detail: 'reported usage',
|
|
2656
|
+
points: points.map((point) => timeSeriesPoint(point, 'actualInputTokens', 'actual tokens'))
|
|
2657
|
+
},
|
|
2658
|
+
{
|
|
2659
|
+
id: 'time-series-uncached-tokens',
|
|
2660
|
+
title: 'Uncached input',
|
|
2661
|
+
value: formatNumber(numberValue(summary.uncachedInputTokens)),
|
|
2662
|
+
detail: 'uncached input tokens',
|
|
2663
|
+
points: points.map((point) => timeSeriesPoint(point, 'uncachedInputTokens', 'uncached input'))
|
|
2664
|
+
}
|
|
2665
|
+
];
|
|
2666
|
+
}
|
|
2667
|
+
const promptBytes = sumJobNumber(jobs, 'promptBytes');
|
|
2668
|
+
const estimatedInputTokens = sumJobNumber(jobs, 'estimatedInputTokens');
|
|
2669
|
+
const actualInputTokens = sumJobNumber(jobs, 'actualInputTokens');
|
|
2670
|
+
const rows = [
|
|
2671
|
+
{
|
|
2672
|
+
id: 'prompt-bytes',
|
|
2673
|
+
title: 'Prompt bytes',
|
|
2674
|
+
value: formatBytes(promptBytes),
|
|
2675
|
+
detail: 'per visible job',
|
|
2676
|
+
points: jobValuePoints(jobs, 'promptBytes', formatBytes)
|
|
2677
|
+
},
|
|
2678
|
+
{
|
|
2679
|
+
id: 'estimated-tokens',
|
|
2680
|
+
title: 'Estimated tokens',
|
|
2681
|
+
value: formatNumber(estimatedInputTokens),
|
|
2682
|
+
detail: 'per visible job',
|
|
2683
|
+
points: jobValuePoints(jobs, 'estimatedInputTokens', formatNumber)
|
|
2684
|
+
}
|
|
2685
|
+
];
|
|
2686
|
+
if (actualInputTokens) {
|
|
2687
|
+
rows.push({
|
|
2688
|
+
id: 'actual-tokens',
|
|
2689
|
+
title: 'Actual tokens',
|
|
2690
|
+
value: formatNumber(actualInputTokens),
|
|
2691
|
+
detail: 'reported usage',
|
|
2692
|
+
points: jobValuePoints(jobs, 'actualInputTokens', formatNumber)
|
|
2693
|
+
});
|
|
2694
|
+
}
|
|
2695
|
+
return rows;
|
|
2696
|
+
}
|
|
2697
|
+
function performanceTabMeta(dashboard, jobs) {
|
|
2698
|
+
const telemetry = tokenTimeSummary(dashboard, jobs);
|
|
2699
|
+
return telemetry.tokenValue === '-' ? 'cost trends' : `${telemetry.tokenValue} input`;
|
|
2700
|
+
}
|
|
2701
|
+
function historyTabMeta(dashboard, jobs, success) {
|
|
2702
|
+
void dashboard;
|
|
2703
|
+
if (success.landedCount)
|
|
2704
|
+
return `${formatNumber(success.landedCount)} landed`;
|
|
2705
|
+
if (success.appliedCount)
|
|
2706
|
+
return `${formatNumber(success.appliedCount)} applied`;
|
|
2707
|
+
return `${formatNumber(jobs.length)} branches`;
|
|
2708
|
+
}
|
|
2709
|
+
function testingTabMeta(jobs) {
|
|
2710
|
+
const summary = testingSummary(jobs, []);
|
|
2711
|
+
if (summary.failedChecks)
|
|
2712
|
+
return `${formatNumber(summary.failedChecks)} failing`;
|
|
2713
|
+
if (summary.activeJobs)
|
|
2714
|
+
return `${formatNumber(summary.activeJobs)} pending`;
|
|
2715
|
+
if (summary.totalChecks)
|
|
2716
|
+
return `${formatNumber(summary.passedChecks)}/${formatNumber(summary.totalChecks)} passing`;
|
|
2717
|
+
if (summary.noMetadataTasks || !jobs.length)
|
|
2718
|
+
return 'no check metadata';
|
|
2719
|
+
return 'quality checks';
|
|
2720
|
+
}
|
|
2721
|
+
function performanceTimeDetail(dashboard) {
|
|
2722
|
+
const points = timeSeriesPoints(dashboard);
|
|
2723
|
+
if (!points.length)
|
|
2724
|
+
return 'per-task fallback';
|
|
2725
|
+
return `${formatNumber(points.length)} ${formatBucketSize(dashboard.timeSeries?.bucketMs)} buckets`;
|
|
2726
|
+
}
|
|
2727
|
+
function historyBranchRows(jobs) {
|
|
2728
|
+
return [...jobs]
|
|
2729
|
+
.sort((left, right) => taskSortRank(left) - taskSortRank(right) || ticketId(left).localeCompare(ticketId(right)))
|
|
2730
|
+
.slice(0, 12);
|
|
2731
|
+
}
|
|
2732
|
+
const HISTORY_GRAPH_COLORS = ['#8b949e', '#58a6ff', '#bc8cff', '#3fb950', '#d29922', '#f778ba', '#39c5cf'];
|
|
2733
|
+
function historyGitGraph(dashboard, jobs) {
|
|
2734
|
+
void dashboard;
|
|
2735
|
+
const ordered = historyGraphJobs(jobs).slice(0, 28);
|
|
2736
|
+
const laneKeys = historyGraphLaneKeys(ordered);
|
|
2737
|
+
const laneGap = 22;
|
|
2738
|
+
const rowHeight = 54;
|
|
2739
|
+
const lanes = [
|
|
2740
|
+
{ id: 'main', label: 'main', x: 16, color: HISTORY_GRAPH_COLORS[0] ?? '#8b949e' },
|
|
2741
|
+
...laneKeys.map((id, index) => ({
|
|
2742
|
+
id,
|
|
2743
|
+
label: id,
|
|
2744
|
+
x: 16 + laneGap * (index + 1),
|
|
2745
|
+
color: HISTORY_GRAPH_COLORS[(index + 1) % HISTORY_GRAPH_COLORS.length] ?? '#58a6ff'
|
|
2746
|
+
}))
|
|
2747
|
+
];
|
|
2748
|
+
const laneById = new Map(lanes.map((lane, index) => [lane.id, { lane, index }]));
|
|
2749
|
+
const rows = ordered.map((job, index) => {
|
|
2750
|
+
const laneId = historyGraphLaneId(job, laneKeys);
|
|
2751
|
+
const laneEntry = laneById.get(laneId) ?? laneById.get('main');
|
|
2752
|
+
const lane = laneEntry?.lane ?? lanes[0];
|
|
2753
|
+
const laneIndex = laneEntry?.index ?? 0;
|
|
2754
|
+
const y = index * rowHeight + Math.round(rowHeight / 2);
|
|
2755
|
+
const tone = historyGraphTone(job);
|
|
2756
|
+
const runtime = jobRuntimeLabel(job, Date.now());
|
|
2757
|
+
const time = historyJobTime(job);
|
|
2758
|
+
const title = `${ticketId(job)} · ${taskTitle(job)}`;
|
|
2759
|
+
const subtitle = `${laneOf(job)} · ${taskCardStatus(job)} · ${pathSummaryText(job)}`;
|
|
2760
|
+
const meta = `${time ? formatTime(time) : 'no timing'} · ${runtime}`;
|
|
2761
|
+
return {
|
|
2762
|
+
id: taskCardId(job),
|
|
2763
|
+
laneId,
|
|
2764
|
+
laneIndex,
|
|
2765
|
+
x: lane.x,
|
|
2766
|
+
y,
|
|
2767
|
+
title,
|
|
2768
|
+
subtitle,
|
|
2769
|
+
meta,
|
|
2770
|
+
tooltip: `${title}\n${subtitle}\n${meta}\n${text(job.disposition ?? job.mergeReadiness ?? job.bucket ?? job.status)}`,
|
|
2771
|
+
tone,
|
|
2772
|
+
merged: historyJobMerged(job)
|
|
2773
|
+
};
|
|
2774
|
+
});
|
|
2775
|
+
return {
|
|
2776
|
+
lanes,
|
|
2777
|
+
rows,
|
|
2778
|
+
width: Math.max(72, 32 + laneGap * Math.max(1, lanes.length)),
|
|
2779
|
+
height: Math.max(rowHeight, rows.length * rowHeight),
|
|
2780
|
+
rowHeight,
|
|
2781
|
+
trunkX: lanes[0]?.x ?? 16
|
|
2782
|
+
};
|
|
2783
|
+
}
|
|
2784
|
+
function historyGraphJobs(jobs) {
|
|
2785
|
+
return [...jobs]
|
|
2786
|
+
.sort((left, right) => historyJobSortTime(right) - historyJobSortTime(left)
|
|
2787
|
+
|| taskSortRank(left) - taskSortRank(right)
|
|
2788
|
+
|| ticketId(left).localeCompare(ticketId(right)));
|
|
2789
|
+
}
|
|
2790
|
+
function historyGraphLaneKeys(jobs) {
|
|
2791
|
+
const raw = uniqueStrings(jobs.map(historyRawLaneKey).filter((value) => value !== 'main'));
|
|
2792
|
+
return raw.slice(0, 6);
|
|
2793
|
+
}
|
|
2794
|
+
function historyGraphLaneId(job, laneKeys) {
|
|
2795
|
+
const raw = historyRawLaneKey(job);
|
|
2796
|
+
if (laneKeys.includes(raw))
|
|
2797
|
+
return raw;
|
|
2798
|
+
if (!laneKeys.length)
|
|
2799
|
+
return 'main';
|
|
2800
|
+
const index = Number.parseInt(stableHash(raw || taskCardId(job)), 36) % laneKeys.length;
|
|
2801
|
+
return laneKeys[index] ?? 'main';
|
|
2802
|
+
}
|
|
2803
|
+
function historyRawLaneKey(job) {
|
|
2804
|
+
const worker = textValue(job.workerId ?? job.agentId ?? job.assignedAgent ?? job.workerName, '');
|
|
2805
|
+
const lane = textValue(job.lane, '');
|
|
2806
|
+
return sanitizeHistoryLane(worker || lane || 'main');
|
|
2807
|
+
}
|
|
2808
|
+
function sanitizeHistoryLane(value) {
|
|
2809
|
+
const clean = value.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 32);
|
|
2810
|
+
return clean || 'main';
|
|
2811
|
+
}
|
|
2812
|
+
function historyGraphTone(job) {
|
|
2813
|
+
if (isFailedJob(job) || isBlockedJob(job) || isStaleJob(job))
|
|
2814
|
+
return 'bad';
|
|
2815
|
+
if (isNeedsCoordinatorReviewJob(job) || taskBoardColumnId(job) === 'review')
|
|
2816
|
+
return 'warn';
|
|
2817
|
+
if (isPositiveProgressJob(job) || isReadyJob(job))
|
|
2818
|
+
return 'good';
|
|
2819
|
+
return 'neutral';
|
|
2820
|
+
}
|
|
2821
|
+
function historyJobMerged(job) {
|
|
2822
|
+
if (isFailedJob(job) || isBlockedJob(job) || isStaleJob(job))
|
|
2823
|
+
return false;
|
|
2824
|
+
return isPositiveProgressJob(job)
|
|
2825
|
+
|| isReadyJob(job)
|
|
2826
|
+
|| normalized(job.bucket) === 'ready-to-apply'
|
|
2827
|
+
|| normalized(job.disposition).includes('applied')
|
|
2828
|
+
|| normalized(job.disposition).includes('landed')
|
|
2829
|
+
|| normalized(job.mergeReadiness).includes('ready');
|
|
2830
|
+
}
|
|
2831
|
+
function historyJobSortTime(job) {
|
|
2832
|
+
return historyJobTime(job) || 0;
|
|
2833
|
+
}
|
|
2834
|
+
function historyJobTime(job) {
|
|
2835
|
+
return timeValue(job.finishedAt)
|
|
2836
|
+
?? timeValue(job.completedAt)
|
|
2837
|
+
?? timeValue(job.endedAt)
|
|
2838
|
+
?? timeValue(job.updatedAt)
|
|
2839
|
+
?? timeValue(job.startedAt)
|
|
2840
|
+
?? timeValue(job.createdAt);
|
|
2841
|
+
}
|
|
2842
|
+
function historyCurvePath(trunkX, laneX, y) {
|
|
2843
|
+
const mid = Math.round((trunkX + laneX) / 2);
|
|
2844
|
+
return `M ${trunkX} ${y} C ${mid} ${y}, ${mid} ${y}, ${laneX} ${y}`;
|
|
2845
|
+
}
|
|
2846
|
+
function historyLaneColor(graph, laneId) {
|
|
2847
|
+
return graph.lanes.find((lane) => lane.id === laneId)?.color ?? HISTORY_GRAPH_COLORS[0] ?? '#8b949e';
|
|
2848
|
+
}
|
|
2849
|
+
function HistoryEventRows({ events }) {
|
|
2850
|
+
const rows = events.slice(-6).reverse();
|
|
2851
|
+
if (!rows.length)
|
|
2852
|
+
return _jsx("p", { className: "empty tight", children: "No recent events are available." });
|
|
2853
|
+
return _jsx("div", { className: "history-events", children: rows.map((event) => _jsxs("article", { className: `history-event ${timelinePointTone(eventToTimelinePoint(event))}`, children: [_jsx("time", { children: formatTime(event.at) }), _jsxs("div", { children: [_jsx("b", { children: text(event.type ?? event.message ?? 'event') }), _jsx("small", { children: text(event.message ?? event.jobId ?? event.lane) })] })] })) });
|
|
2854
|
+
}
|
|
2855
|
+
function eventToTimelinePoint(event) {
|
|
2856
|
+
return {
|
|
2857
|
+
at: numberValue(event.at),
|
|
2858
|
+
type: textValue(event.type, ''),
|
|
2859
|
+
lane: textValue(event.lane, ''),
|
|
2860
|
+
jobId: textValue(event.jobId, ''),
|
|
2861
|
+
message: textValue(event.message, ''),
|
|
2862
|
+
progressPercent: 0
|
|
2863
|
+
};
|
|
2864
|
+
}
|
|
2865
|
+
function testingSummary(jobs, events) {
|
|
2866
|
+
const signals = jobs.map(testingJobSignals);
|
|
2867
|
+
const passedChecks = signals.reduce((sum, signal) => sum + signal.passedChecks, 0);
|
|
2868
|
+
const failedChecks = signals.reduce((sum, signal) => sum + signal.failedChecks, 0);
|
|
2869
|
+
const evidencePaths = signals.reduce((sum, signal) => sum + signal.evidencePaths.length, 0);
|
|
2870
|
+
const browserEvidence = signals.filter((signal) => signal.browserEvidence).length;
|
|
2871
|
+
const fuzzEvidence = signals.filter((signal) => signal.fuzzEvidence).length;
|
|
2872
|
+
const oracleEvidence = signals.filter((signal) => signal.oracleEvidence).length;
|
|
2873
|
+
const unitEvidence = signals.filter((signal) => signal.unitEvidence).length;
|
|
2874
|
+
const activeJobs = signals.filter((signal) => signal.active).length;
|
|
2875
|
+
const noMetadataTasks = signals.filter((signal) => !signal.hasMetadata).length;
|
|
2876
|
+
const openFailures = jobs.filter((job) => isFailedJob(job) || isBlockedJob(job) || isStaleJob(job)).length;
|
|
2877
|
+
const totalChecks = passedChecks + failedChecks;
|
|
2878
|
+
const rows = signals
|
|
2879
|
+
.filter((signal) => signal.passedChecks || signal.failedChecks || signal.evidencePaths.length || signal.openFailure || signal.active || !signal.hasMetadata)
|
|
2880
|
+
.sort((left, right) => Number(right.openFailure) - Number(left.openFailure)
|
|
2881
|
+
|| Number(right.active) - Number(left.active)
|
|
2882
|
+
|| Number(!right.hasMetadata) - Number(!left.hasMetadata)
|
|
2883
|
+
|| right.failedChecks - left.failedChecks
|
|
2884
|
+
|| right.passedChecks - left.passedChecks
|
|
2885
|
+
|| left.label.localeCompare(right.label))
|
|
2886
|
+
.slice(0, 10)
|
|
2887
|
+
.map((signal) => ({
|
|
2888
|
+
label: signal.label,
|
|
2889
|
+
value: signal.failedChecks
|
|
2890
|
+
? `${formatNumber(signal.failedChecks)} failed`
|
|
2891
|
+
: signal.passedChecks
|
|
2892
|
+
? `${formatNumber(signal.passedChecks)} passed`
|
|
2893
|
+
: signal.active
|
|
2894
|
+
? 'pending'
|
|
2895
|
+
: signal.hasMetadata
|
|
2896
|
+
? `${formatNumber(signal.evidencePaths.length)} evidence`
|
|
2897
|
+
: 'no metadata',
|
|
2898
|
+
detailParts: signal.detailParts,
|
|
2899
|
+
tone: signal.failedChecks || signal.openFailure
|
|
2900
|
+
? 'bad'
|
|
2901
|
+
: signal.active || !signal.hasMetadata
|
|
2902
|
+
? 'warn'
|
|
2903
|
+
: signal.passedChecks
|
|
2904
|
+
? 'good'
|
|
2905
|
+
: 'neutral'
|
|
2906
|
+
}));
|
|
2907
|
+
const testEvents = events.filter((event) => /\b(test|fuzz|oracle|playwright|smoke|spec)\b/i.test(`${text(event.type)} ${text(event.message)}`)).length;
|
|
2908
|
+
const status = failedChecks || openFailures
|
|
2909
|
+
? 'attention needed'
|
|
2910
|
+
: activeJobs
|
|
2911
|
+
? 'active work pending verification'
|
|
2912
|
+
: totalChecks
|
|
2913
|
+
? 'checks reported'
|
|
2914
|
+
: evidencePaths
|
|
2915
|
+
? 'evidence reported; check results missing'
|
|
2916
|
+
: 'no check metadata yet';
|
|
2917
|
+
const notice = failedChecks || openFailures
|
|
2918
|
+
? undefined
|
|
2919
|
+
: activeJobs
|
|
2920
|
+
? `${formatNumber(activeJobs)} active ${activeJobs === 1 ? 'task is' : 'tasks are'} still running; do not treat this snapshot as fully verified.`
|
|
2921
|
+
: totalChecks
|
|
2922
|
+
? undefined
|
|
2923
|
+
: evidencePaths
|
|
2924
|
+
? 'Evidence paths are present, but no pass/fail command metadata was reported.'
|
|
2925
|
+
: 'No testing metadata was reported. This empty state means unknown, not passing.';
|
|
2926
|
+
return {
|
|
2927
|
+
totalChecks,
|
|
2928
|
+
passedChecks,
|
|
2929
|
+
failedChecks,
|
|
2930
|
+
evidenceTasks: signals.filter((signal) => signal.evidencePaths.length > 0).length,
|
|
2931
|
+
evidencePaths,
|
|
2932
|
+
browserEvidence,
|
|
2933
|
+
fuzzEvidence,
|
|
2934
|
+
oracleEvidence,
|
|
2935
|
+
unitEvidence: unitEvidence || testEvents,
|
|
2936
|
+
activeJobs,
|
|
2937
|
+
noMetadataTasks,
|
|
2938
|
+
openFailures,
|
|
2939
|
+
status,
|
|
2940
|
+
notice,
|
|
2941
|
+
noticeTone: activeJobs || noMetadataTasks ? 'warn' : 'neutral',
|
|
2942
|
+
rows
|
|
2943
|
+
};
|
|
2944
|
+
}
|
|
2945
|
+
function testingJobSignals(job) {
|
|
2946
|
+
const passed = arrayRecords(job.commandsPassed);
|
|
2947
|
+
const failed = arrayRecords(job.commandsFailed);
|
|
2948
|
+
const evidencePaths = uniqueStrings([
|
|
2949
|
+
...stringArray(job.evidencePaths),
|
|
2950
|
+
...stringArray(job.proofPaths),
|
|
2951
|
+
...stringArray(job.artifactPaths)
|
|
2952
|
+
]);
|
|
2953
|
+
const evidenceText = evidencePaths.join(' ').toLowerCase();
|
|
2954
|
+
const commandText = [...passed, ...failed].map((entry) => text(entry.command ?? entry.name ?? entry.summary)).join(' ').toLowerCase();
|
|
2955
|
+
const combined = `${evidenceText} ${commandText}`;
|
|
2956
|
+
const passedChecks = passed.length + numberValue(job.commandsPassedCount ?? job.passedCommandCount ?? job.testPassedCount);
|
|
2957
|
+
const failedChecks = failed.length + numberValue(job.commandsFailedCount ?? job.failedCommandCount ?? job.testFailedCount);
|
|
2958
|
+
const hasCommandMetadata = hasOwn(job, 'commandsPassed')
|
|
2959
|
+
|| hasOwn(job, 'commandsFailed')
|
|
2960
|
+
|| hasOwn(job, 'commandsPassedCount')
|
|
2961
|
+
|| hasOwn(job, 'passedCommandCount')
|
|
2962
|
+
|| hasOwn(job, 'testPassedCount')
|
|
2963
|
+
|| hasOwn(job, 'commandsFailedCount')
|
|
2964
|
+
|| hasOwn(job, 'failedCommandCount')
|
|
2965
|
+
|| hasOwn(job, 'testFailedCount');
|
|
2966
|
+
const hasMetadata = hasCommandMetadata || evidencePaths.length > 0;
|
|
2967
|
+
return {
|
|
2968
|
+
label: `${ticketId(job)} · ${taskTitle(job)}`,
|
|
2969
|
+
detailParts: [
|
|
2970
|
+
laneOf(job),
|
|
2971
|
+
taskCardStatus(job),
|
|
2972
|
+
hasMetadata ? evidencePaths.length ? `${formatNumber(evidencePaths.length)} evidence paths` : 'command metadata only' : 'no check metadata'
|
|
2973
|
+
],
|
|
2974
|
+
passedChecks,
|
|
2975
|
+
failedChecks,
|
|
2976
|
+
evidencePaths,
|
|
2977
|
+
browserEvidence: /\b(browser|playwright|screenshot|dom|visual)\b/.test(combined),
|
|
2978
|
+
fuzzEvidence: /\b(fuzz|property|random|quickcheck)\b/.test(combined),
|
|
2979
|
+
oracleEvidence: /\b(oracle|golden|reference|fixture|snapshot)\b/.test(combined),
|
|
2980
|
+
unitEvidence: /\b(test|smoke|spec|vitest|jest|node --test|npm test)\b/.test(combined),
|
|
2981
|
+
openFailure: isFailedJob(job) || isBlockedJob(job) || isStaleJob(job),
|
|
2982
|
+
active: isActiveAgentJob(job),
|
|
2983
|
+
hasMetadata
|
|
2984
|
+
};
|
|
2985
|
+
}
|
|
2986
|
+
function hasOwn(record, key) {
|
|
2987
|
+
return Object.prototype.hasOwnProperty.call(record, key);
|
|
2988
|
+
}
|
|
2989
|
+
function testingEvidenceCard(label, value, detail) {
|
|
2990
|
+
return _jsxs("article", { className: "testing-evidence-card", children: [_jsx("span", { children: label }), _jsx("b", { children: value ? formatNumber(value) : '-' }), _jsx("small", { children: detail })] });
|
|
2991
|
+
}
|
|
2992
|
+
function performanceTimeChartSeries(dashboard, jobs) {
|
|
2993
|
+
const points = timeSeriesPoints(dashboard);
|
|
2994
|
+
if (!points.length)
|
|
2995
|
+
return performanceJobFallbackSeries(jobs);
|
|
2996
|
+
const summary = recordValue(dashboard.timeSeries?.summary);
|
|
2997
|
+
const bucketLabel = `${formatBucketSize(dashboard.timeSeries?.bucketMs)} buckets`;
|
|
2998
|
+
const actualInput = numberValue(summary.actualInputTokens);
|
|
2999
|
+
const estimatedInput = numberValue(summary.estimatedInputTokens);
|
|
3000
|
+
const cachedInput = numberValue(summary.cachedInputTokens);
|
|
3001
|
+
const durationMs = numberValue(summary.durationMs);
|
|
3002
|
+
const cacheHit = actualInput > 0 ? cachedInput / actualInput : 0;
|
|
3003
|
+
const waste = numberValue(summary.warningJobCount)
|
|
3004
|
+
+ numberValue(summary.failureJobCount)
|
|
3005
|
+
+ numberValue(summary.blockedJobCount)
|
|
3006
|
+
+ numberValue(summary.contextBudgetWarningCount)
|
|
3007
|
+
+ numberValue(summary.contextBudgetFailedCount);
|
|
3008
|
+
const inputKey = actualInput ? 'actualInputTokens' : 'estimatedInputTokens';
|
|
3009
|
+
return [
|
|
3010
|
+
{
|
|
3011
|
+
id: 'performance-input-tokens',
|
|
3012
|
+
title: actualInput ? 'Input tokens' : 'Estimated input',
|
|
3013
|
+
value: actualInput ? formatNumber(actualInput) : formatNumber(estimatedInput),
|
|
3014
|
+
detail: actualInput ? `${formatNumber(cachedInput)} cached · ${formatNumber(numberValue(summary.uncachedInputTokens))} uncached` : 'estimated by bucket',
|
|
3015
|
+
xLabel: bucketLabel,
|
|
3016
|
+
yLabel: 'tokens',
|
|
3017
|
+
points: points.map((point) => timeSeriesPoint(point, inputKey, actualInput ? 'input tokens' : 'estimated input'))
|
|
3018
|
+
},
|
|
3019
|
+
{
|
|
3020
|
+
id: 'performance-runtime',
|
|
3021
|
+
title: 'Runtime',
|
|
3022
|
+
value: durationMs ? formatDuration(durationMs) : '-',
|
|
3023
|
+
detail: 'bucketed worker duration',
|
|
3024
|
+
xLabel: bucketLabel,
|
|
3025
|
+
yLabel: 'duration',
|
|
3026
|
+
points: points.map((point) => timeSeriesPoint(point, 'durationMs', 'runtime', 'neutral', formatDuration))
|
|
3027
|
+
},
|
|
3028
|
+
{
|
|
3029
|
+
id: 'performance-cache-hit',
|
|
3030
|
+
title: 'Cache hit',
|
|
3031
|
+
value: cacheHit ? formatPercent(cacheHit) : '-',
|
|
3032
|
+
detail: cachedInput ? `${formatNumber(cachedInput)} cached tokens` : 'no cache data',
|
|
3033
|
+
tone: cacheHit >= 0.25 ? 'good' : cacheHit ? 'warn' : 'neutral',
|
|
3034
|
+
xLabel: bucketLabel,
|
|
3035
|
+
yLabel: 'percent cached',
|
|
3036
|
+
points: points.map(cacheHitPoint)
|
|
3037
|
+
},
|
|
3038
|
+
{
|
|
3039
|
+
id: 'performance-waste',
|
|
3040
|
+
title: 'Waste signals',
|
|
3041
|
+
value: formatNumber(waste),
|
|
3042
|
+
detail: 'warnings, failures, blocked, and budget signals',
|
|
3043
|
+
tone: waste ? 'warn' : 'good',
|
|
3044
|
+
xLabel: bucketLabel,
|
|
3045
|
+
yLabel: 'signal count',
|
|
3046
|
+
points: points.map(wasteSignalPoint)
|
|
3047
|
+
}
|
|
3048
|
+
];
|
|
3049
|
+
}
|
|
3050
|
+
function performanceJobFallbackSeries(jobs) {
|
|
3051
|
+
const context = contextPressureSummary(jobs);
|
|
3052
|
+
return [
|
|
3053
|
+
{
|
|
3054
|
+
id: 'performance-job-input',
|
|
3055
|
+
title: 'Input tokens',
|
|
3056
|
+
value: context.actualInputTokens ? formatNumber(context.actualInputTokens) : formatNumber(context.estimatedInputTokens),
|
|
3057
|
+
detail: 'per-task usage fallback',
|
|
3058
|
+
xLabel: 'tasks',
|
|
3059
|
+
yLabel: 'tokens',
|
|
3060
|
+
points: jobValuePoints(jobs, context.actualInputTokens ? 'actualInputTokens' : 'estimatedInputTokens', formatNumber)
|
|
3061
|
+
},
|
|
3062
|
+
{
|
|
3063
|
+
id: 'performance-job-runtime',
|
|
3064
|
+
title: 'Runtime',
|
|
3065
|
+
value: formatDuration(sumJobNumber(jobs, 'durationMs')),
|
|
3066
|
+
detail: 'per-task duration fallback',
|
|
3067
|
+
xLabel: 'tasks',
|
|
3068
|
+
yLabel: 'duration',
|
|
3069
|
+
points: jobValuePoints(jobs, 'durationMs', formatDuration)
|
|
3070
|
+
},
|
|
3071
|
+
{
|
|
3072
|
+
id: 'performance-job-cache-hit',
|
|
3073
|
+
title: 'Cache hit',
|
|
3074
|
+
value: context.cacheHitRatio ? formatPercent(context.cacheHitRatio) : '-',
|
|
3075
|
+
detail: context.cachedInputTokens ? `${formatNumber(context.cachedInputTokens)} cached tokens` : 'no cache data',
|
|
3076
|
+
tone: context.cacheHitRatio >= 0.25 ? 'good' : context.cacheHitRatio ? 'warn' : 'neutral',
|
|
3077
|
+
xLabel: 'tasks',
|
|
3078
|
+
yLabel: 'percent cached',
|
|
3079
|
+
points: jobs.slice(-18).map(cacheHitJobPoint)
|
|
3080
|
+
},
|
|
3081
|
+
{
|
|
3082
|
+
id: 'performance-job-waste',
|
|
3083
|
+
title: 'Waste signals',
|
|
3084
|
+
value: formatNumber(jobs.filter((job) => isFailedJob(job) || isBlockedJob(job) || isContextBudgetWarningJob(job) || isContextBudgetFailedJob(job)).length),
|
|
3085
|
+
detail: 'per-task warning fallback',
|
|
3086
|
+
tone: jobs.some((job) => isFailedJob(job) || isBlockedJob(job) || isContextBudgetWarningJob(job) || isContextBudgetFailedJob(job)) ? 'warn' : 'good',
|
|
3087
|
+
xLabel: 'tasks',
|
|
3088
|
+
yLabel: 'signal count',
|
|
3089
|
+
points: jobs.slice(-18).map(wasteSignalJobPoint)
|
|
3090
|
+
}
|
|
3091
|
+
];
|
|
3092
|
+
}
|
|
3093
|
+
function cacheHitPoint(point) {
|
|
3094
|
+
const actual = numberValue(point.actualInputTokens);
|
|
3095
|
+
const cached = numberValue(point.cachedInputTokens);
|
|
3096
|
+
const ratio = actual > 0 && cached > 0 ? cached / actual : 0;
|
|
3097
|
+
return {
|
|
3098
|
+
label: formatTime(point.at),
|
|
3099
|
+
value: Math.round(ratio * 100),
|
|
3100
|
+
detail: ratio ? `${formatPercent(ratio)} cache hit` : 'no cache hit reported',
|
|
3101
|
+
tone: ratio >= 0.25 ? 'good' : ratio ? 'warn' : 'neutral'
|
|
3102
|
+
};
|
|
3103
|
+
}
|
|
3104
|
+
function cacheHitJobPoint(job) {
|
|
3105
|
+
const actual = numberValue(job.actualInputTokens);
|
|
3106
|
+
const cached = numberValue(job.cachedInputTokens);
|
|
3107
|
+
const ratio = actual > 0 && cached > 0 ? cached / actual : 0;
|
|
3108
|
+
return {
|
|
3109
|
+
label: textValue(job.id ?? job.taskId ?? job.title, 'job'),
|
|
3110
|
+
value: Math.round(ratio * 100),
|
|
3111
|
+
detail: ratio ? `${formatPercent(ratio)} cache hit` : 'no cache hit reported',
|
|
3112
|
+
tone: ratio >= 0.25 ? 'good' : ratio ? 'warn' : 'neutral'
|
|
3113
|
+
};
|
|
3114
|
+
}
|
|
3115
|
+
function wasteSignalPoint(point) {
|
|
3116
|
+
const value = numberValue(point.warningJobCount)
|
|
3117
|
+
+ numberValue(point.failureJobCount)
|
|
3118
|
+
+ numberValue(point.blockedJobCount)
|
|
3119
|
+
+ numberValue(point.contextBudgetWarningCount)
|
|
3120
|
+
+ numberValue(point.contextBudgetFailedCount);
|
|
3121
|
+
return {
|
|
3122
|
+
label: formatTime(point.at),
|
|
3123
|
+
value,
|
|
3124
|
+
detail: `${formatNumber(value)} waste signals`,
|
|
3125
|
+
tone: value ? 'warn' : 'good'
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
3128
|
+
function wasteSignalJobPoint(job) {
|
|
3129
|
+
const value = (isFailedJob(job) ? 1 : 0)
|
|
3130
|
+
+ (isBlockedJob(job) ? 1 : 0)
|
|
3131
|
+
+ (isContextBudgetWarningJob(job) ? 1 : 0)
|
|
3132
|
+
+ (isContextBudgetFailedJob(job) ? 1 : 0);
|
|
3133
|
+
return {
|
|
3134
|
+
label: textValue(job.id ?? job.taskId ?? job.title, 'job'),
|
|
3135
|
+
value,
|
|
3136
|
+
detail: value ? `${formatNumber(value)} waste signals` : 'no waste signal',
|
|
3137
|
+
tone: value ? 'warn' : 'good'
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
function performanceWasteRows(jobs, attention, audit, context) {
|
|
3141
|
+
return [
|
|
3142
|
+
{ label: 'Budget warnings', value: context.warningCount, detail: `${formatNumber(context.failedCount)} failed budget`, tone: context.warningCount || context.failedCount ? 'warn' : 'neutral' },
|
|
3143
|
+
{ label: 'Coordinator review', value: attention.failedCount + attention.needsCoordinatorReviewCount + attention.staleCount, detail: `${formatNumber(attention.failedCount)} failed · ${formatNumber(attention.needsCoordinatorReviewCount)} coordinator · ${formatNumber(attention.staleCount)} stale`, tone: attention.failedCount + attention.needsCoordinatorReviewCount + attention.staleCount ? 'warn' : 'neutral' },
|
|
3144
|
+
{ label: 'Blocked', value: attention.blockedCount, detail: 'explicit dependency or impossible-task blocks', tone: attention.blockedCount ? 'bad' : 'neutral' },
|
|
3145
|
+
{ label: 'Coordinator review', value: attention.needsCoordinatorReviewCount, detail: 'needs coordinator review', tone: attention.needsCoordinatorReviewCount ? 'review' : 'neutral' },
|
|
3146
|
+
{ label: 'Generated noise', value: generatedNoiseCount(audit), detail: 'ignored generated/cache output', tone: generatedNoiseCount(audit) ? 'warn' : 'neutral' },
|
|
3147
|
+
{ label: 'P95 uncached', value: context.p95UncachedInputTokens, detail: formatNumber(context.p95UncachedInputTokens), tone: context.p95UncachedInputTokens ? 'warn' : 'neutral' },
|
|
3148
|
+
{ label: 'Tasks without timing', value: jobs.filter((job) => !jobRuntimeMs(job, Date.now())).length, detail: 'missing duration/start/end', tone: jobs.some((job) => !jobRuntimeMs(job, Date.now())) ? 'warn' : 'neutral' }
|
|
3149
|
+
];
|
|
3150
|
+
}
|
|
3151
|
+
function optimizationSignalSummary(dashboard, jobs) {
|
|
3152
|
+
const summary = dashboard.summary ?? {};
|
|
3153
|
+
const routing = recordValue(dashboard.routing);
|
|
3154
|
+
const panelSignals = firstPositiveNumber(routing.panelDecisionCount, routing.panelResultCount, summary.panelDecisionCount, summary.panelResultCount, summary.fusionPanelCount);
|
|
3155
|
+
const tournamentObservations = firstPositiveNumber(routing.tournamentObservationCount, summary.tournamentObservationCount);
|
|
3156
|
+
const tournamentRecommendations = firstPositiveNumber(routing.tournamentRecommendationCount, summary.tournamentRecommendationCount);
|
|
3157
|
+
const routingFeedback = firstPositiveNumber(routing.feedbackCount, routing.preferenceCount, summary.routingFeedbackCount, summary.routingPreferenceCount);
|
|
3158
|
+
const rsiSignals = firstPositiveNumber(routing.rsiSignalCount, routing.selfOptimizationCount, summary.rsiSignalCount, summary.selfOptimizationCount);
|
|
3159
|
+
const models = uniqueStrings(jobs.map(agentModelLabel).filter((model) => model !== 'model unknown'));
|
|
3160
|
+
const usefulOutput = jobs.length ? successLikeJobCount(jobs) / jobs.length : 0;
|
|
3161
|
+
const available = panelSignals + tournamentObservations + tournamentRecommendations + routingFeedback + rsiSignals;
|
|
3162
|
+
return {
|
|
3163
|
+
status: available ? 'optimization telemetry available' : 'no panel/tournament/RSI telemetry yet',
|
|
3164
|
+
rows: [
|
|
3165
|
+
{
|
|
3166
|
+
label: 'Panel decisions',
|
|
3167
|
+
value: panelSignals ? formatNumber(panelSignals) : '-',
|
|
3168
|
+
detail: panelSignals ? 'panel/fusion outputs reported by the run' : 'not emitted by this run yet',
|
|
3169
|
+
tone: panelSignals ? 'good' : 'neutral'
|
|
3170
|
+
},
|
|
3171
|
+
{
|
|
3172
|
+
label: 'Tournament signals',
|
|
3173
|
+
value: tournamentObservations || tournamentRecommendations ? `${formatNumber(tournamentObservations)}/${formatNumber(tournamentRecommendations)}` : '-',
|
|
3174
|
+
detail: 'observations / recommendations',
|
|
3175
|
+
tone: tournamentObservations || tournamentRecommendations ? 'good' : 'neutral'
|
|
3176
|
+
},
|
|
3177
|
+
{
|
|
3178
|
+
label: 'RSI / routing feedback',
|
|
3179
|
+
value: routingFeedback || rsiSignals ? formatNumber(routingFeedback + rsiSignals) : '-',
|
|
3180
|
+
detail: routingFeedback || rsiSignals ? 'feedback available for future routing' : 'answers are not yet fed into model routing here',
|
|
3181
|
+
tone: routingFeedback || rsiSignals ? 'good' : 'neutral'
|
|
3182
|
+
},
|
|
3183
|
+
{
|
|
3184
|
+
label: 'Model diversity',
|
|
3185
|
+
value: models.length ? formatNumber(models.length) : '-',
|
|
3186
|
+
detail: models.length ? models.slice(0, 3).join(', ') : 'no model labels reported',
|
|
3187
|
+
tone: models.length > 1 ? 'good' : 'neutral'
|
|
3188
|
+
},
|
|
3189
|
+
{
|
|
3190
|
+
label: 'Useful output',
|
|
3191
|
+
value: jobs.length ? formatPercent(usefulOutput) : '-',
|
|
3192
|
+
detail: `${formatNumber(successLikeJobCount(jobs))} useful of ${formatNumber(jobs.length)}`,
|
|
3193
|
+
tone: usefulOutput >= 0.8 ? 'good' : usefulOutput ? 'warn' : 'neutral'
|
|
3194
|
+
}
|
|
3195
|
+
]
|
|
3196
|
+
};
|
|
3197
|
+
}
|
|
3198
|
+
function optimizationBehaviorSummary(dashboard, jobs) {
|
|
3199
|
+
const summary = dashboard.summary ?? {};
|
|
3200
|
+
const routing = recordValue(dashboard.routing);
|
|
3201
|
+
const points = timeSeriesPoints(dashboard);
|
|
3202
|
+
const panelSignals = currentPanelSignalCount(routing, summary);
|
|
3203
|
+
const tournamentObservations = firstPositiveNumber(routing.tournamentObservationCount, summary.tournamentObservationCount);
|
|
3204
|
+
const tournamentRecommendations = firstPositiveNumber(routing.tournamentRecommendationCount, summary.tournamentRecommendationCount);
|
|
3205
|
+
const routingFeedback = firstPositiveNumber(routing.feedbackCount, routing.preferenceCount, summary.routingFeedbackCount, summary.routingPreferenceCount);
|
|
3206
|
+
const rsiSignals = firstPositiveNumber(routing.rsiSignalCount, routing.selfOptimizationCount, summary.rsiSignalCount, summary.selfOptimizationCount);
|
|
3207
|
+
const available = panelSignals + tournamentObservations + tournamentRecommendations + routingFeedback + rsiSignals;
|
|
3208
|
+
const signalBuckets = points.filter((point) => (optimizationBucketSignalCount(point) ?? 0) > 0).length;
|
|
3209
|
+
const rows = optimizationBehaviorRows(points, jobs);
|
|
3210
|
+
const improving = rows.filter((row) => row.tone === 'good').length;
|
|
3211
|
+
const comparable = rows.filter((row) => row.tone !== 'neutral').length;
|
|
3212
|
+
const status = !available
|
|
3213
|
+
? 'no optimization telemetry yet'
|
|
3214
|
+
: !points.length
|
|
3215
|
+
? 'current optimization totals only'
|
|
3216
|
+
: !signalBuckets
|
|
3217
|
+
? `${formatNumber(available)} current signals; no bucketed signal trend`
|
|
3218
|
+
: `${formatNumber(improving)}/${formatNumber(comparable || rows.length)} workflow indicators improving`;
|
|
3219
|
+
return {
|
|
3220
|
+
status,
|
|
3221
|
+
cards: [
|
|
3222
|
+
{
|
|
3223
|
+
label: 'Panel influence',
|
|
3224
|
+
value: panelSignals ? formatNumber(panelSignals) : '-',
|
|
3225
|
+
detail: panelSignals ? 'panel/fusion decisions reported' : 'no panel fields reported',
|
|
3226
|
+
tone: panelSignals ? 'good' : 'neutral'
|
|
3227
|
+
},
|
|
3228
|
+
{
|
|
3229
|
+
label: 'Tournament loop',
|
|
3230
|
+
value: tournamentObservations || tournamentRecommendations ? `${formatNumber(tournamentObservations)}/${formatNumber(tournamentRecommendations)}` : '-',
|
|
3231
|
+
detail: 'observations / recommendations',
|
|
3232
|
+
tone: tournamentObservations || tournamentRecommendations ? 'good' : 'neutral'
|
|
3233
|
+
},
|
|
3234
|
+
{
|
|
3235
|
+
label: 'RSI routing loop',
|
|
3236
|
+
value: routingFeedback || rsiSignals ? formatNumber(routingFeedback + rsiSignals) : '-',
|
|
3237
|
+
detail: routingFeedback || rsiSignals ? `${formatNumber(routingFeedback)} feedback · ${formatNumber(rsiSignals)} RSI` : 'no feedback fields reported',
|
|
3238
|
+
tone: routingFeedback || rsiSignals ? 'good' : 'neutral'
|
|
3239
|
+
},
|
|
3240
|
+
{
|
|
3241
|
+
label: 'Trend coverage',
|
|
3242
|
+
value: points.length ? `${formatNumber(signalBuckets)}/${formatNumber(points.length)}` : '-',
|
|
3243
|
+
detail: points.length ? 'buckets with optimization counters' : 'time series unavailable',
|
|
3244
|
+
tone: signalBuckets ? 'good' : points.length ? 'warn' : 'neutral'
|
|
3245
|
+
}
|
|
3246
|
+
],
|
|
3247
|
+
rows
|
|
3248
|
+
};
|
|
3249
|
+
}
|
|
3250
|
+
function optimizationBehaviorRows(points, jobs) {
|
|
3251
|
+
const signalTrend = bucketTrend(points, optimizationBucketSignalCount, false);
|
|
3252
|
+
const wasteTrend = bucketTrend(points, optimizationBucketWasteCount, true);
|
|
3253
|
+
const cacheTrend = bucketTrend(points, optimizationBucketCacheRatio, false);
|
|
3254
|
+
const usefulTrend = bucketTrend(points, optimizationBucketUsefulRatio, false);
|
|
3255
|
+
const fallbackUseful = jobs.length ? successLikeJobCount(jobs) / jobs.length : 0;
|
|
3256
|
+
return [
|
|
3257
|
+
trendRow('Optimization signal cadence', signalTrend, 'panel, tournament, RSI, and routing feedback counters by bucket', formatNumber, 'no bucketed optimization counters'),
|
|
3258
|
+
trendRow('Waste pressure', wasteTrend, 'warnings, failures, blocked jobs, and context budget signals', formatNumber, 'no bucketed waste counters'),
|
|
3259
|
+
trendRow('Cache behavior', cacheTrend, 'cached input share by bucket', formatPercent, 'no bucketed cache data'),
|
|
3260
|
+
usefulTrend.hasData
|
|
3261
|
+
? trendRow('Useful throughput', usefulTrend, 'healthy terminal share by bucket', formatPercent, 'no bucketed terminal data')
|
|
3262
|
+
: {
|
|
3263
|
+
label: 'Useful throughput',
|
|
3264
|
+
value: jobs.length ? formatPercent(fallbackUseful) : '-',
|
|
3265
|
+
detail: jobs.length ? `${formatNumber(successLikeJobCount(jobs))} useful of ${formatNumber(jobs.length)} visible tasks; no bucketed terminal trend` : 'no visible tasks',
|
|
3266
|
+
tone: 'neutral'
|
|
3267
|
+
}
|
|
3268
|
+
];
|
|
3269
|
+
}
|
|
3270
|
+
function trendRow(label, trend, detail, formatter, unavailable) {
|
|
3271
|
+
if (!trend.hasData)
|
|
3272
|
+
return { label, value: '-', detail: unavailable, tone: 'neutral' };
|
|
3273
|
+
const direction = trend.delta === 0 ? 'flat' : trend.improving ? 'improving' : 'worsening';
|
|
3274
|
+
return {
|
|
3275
|
+
label,
|
|
3276
|
+
value: trendDeltaLabel(trend.delta, formatter),
|
|
3277
|
+
detail: `${direction}; ${formatter(trend.first)} early to ${formatter(trend.last)} recent · ${detail}`,
|
|
3278
|
+
tone: trend.delta === 0 ? 'neutral' : trend.improving ? 'good' : 'warn'
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
function bucketTrend(points, valueOf, lowerIsBetter) {
|
|
3282
|
+
const values = points.map(valueOf).filter((value) => value !== undefined && Number.isFinite(value));
|
|
3283
|
+
if (values.length < 2)
|
|
3284
|
+
return { hasData: false, first: 0, last: 0, delta: 0, improving: false };
|
|
3285
|
+
const split = Math.max(1, Math.floor(values.length / 2));
|
|
3286
|
+
const first = average(values.slice(0, split));
|
|
3287
|
+
const last = average(values.slice(split));
|
|
3288
|
+
const delta = last - first;
|
|
3289
|
+
return { hasData: true, first, last, delta, improving: lowerIsBetter ? delta < 0 : delta > 0 };
|
|
3290
|
+
}
|
|
3291
|
+
function optimizationBucketSignalCount(point) {
|
|
3292
|
+
const panel = firstKnownBucketNumber(point, ['panelDecisionCount', 'panelResultCount', 'fusionPanelCount']);
|
|
3293
|
+
const tournament = sumKnownBucketNumbers(point, ['tournamentObservationCount', 'tournamentRecommendationCount']);
|
|
3294
|
+
const feedback = sumKnownBucketNumbers(point, ['feedbackCount', 'preferenceCount', 'routingFeedbackCount', 'routingPreferenceCount', 'rsiSignalCount', 'selfOptimizationCount']);
|
|
3295
|
+
if (panel === undefined && tournament === undefined && feedback === undefined)
|
|
3296
|
+
return undefined;
|
|
3297
|
+
return (panel ?? 0) + (tournament ?? 0) + (feedback ?? 0);
|
|
3298
|
+
}
|
|
3299
|
+
function optimizationBucketWasteCount(point) {
|
|
3300
|
+
return sumKnownBucketNumbers(point, ['warningJobCount', 'failureJobCount', 'blockedJobCount', 'contextBudgetWarningCount', 'contextBudgetFailedCount']);
|
|
3301
|
+
}
|
|
3302
|
+
function optimizationBucketCacheRatio(point) {
|
|
3303
|
+
if (!hasNumberField(point, 'actualInputTokens') || !hasNumberField(point, 'cachedInputTokens'))
|
|
3304
|
+
return undefined;
|
|
3305
|
+
const actual = numberValue(point.actualInputTokens);
|
|
3306
|
+
const cached = numberValue(point.cachedInputTokens);
|
|
3307
|
+
return actual > 0 ? cached / actual : 0;
|
|
3308
|
+
}
|
|
3309
|
+
function optimizationBucketUsefulRatio(point) {
|
|
3310
|
+
if (!hasNumberField(point, 'terminalJobCount'))
|
|
3311
|
+
return undefined;
|
|
3312
|
+
const terminal = numberValue(point.terminalJobCount);
|
|
3313
|
+
if (!terminal)
|
|
3314
|
+
return 0;
|
|
3315
|
+
const warnings = numberValue(point.warningJobCount);
|
|
3316
|
+
const failures = numberValue(point.failureJobCount);
|
|
3317
|
+
const blocked = numberValue(point.blockedJobCount);
|
|
3318
|
+
return Math.max(0, terminal - warnings - failures - blocked) / terminal;
|
|
3319
|
+
}
|
|
3320
|
+
function currentPanelSignalCount(routing, summary) {
|
|
3321
|
+
return firstPositiveNumber(routing.panelDecisionCount, routing.panelResultCount, summary.panelDecisionCount, summary.panelResultCount, summary.fusionPanelCount);
|
|
3322
|
+
}
|
|
3323
|
+
function firstKnownBucketNumber(point, keys) {
|
|
3324
|
+
for (const key of keys) {
|
|
3325
|
+
if (hasNumberField(point, key))
|
|
3326
|
+
return numberValue(point[key]);
|
|
3327
|
+
}
|
|
3328
|
+
return undefined;
|
|
3329
|
+
}
|
|
3330
|
+
function sumKnownBucketNumbers(point, keys) {
|
|
3331
|
+
let hasValue = false;
|
|
3332
|
+
let sum = 0;
|
|
3333
|
+
for (const key of keys) {
|
|
3334
|
+
if (!hasNumberField(point, key))
|
|
3335
|
+
continue;
|
|
3336
|
+
hasValue = true;
|
|
3337
|
+
sum += numberValue(point[key]);
|
|
3338
|
+
}
|
|
3339
|
+
return hasValue ? sum : undefined;
|
|
3340
|
+
}
|
|
3341
|
+
function hasNumberField(record, key) {
|
|
3342
|
+
return record[key] !== undefined && record[key] !== null && record[key] !== '' && Number.isFinite(Number(record[key]));
|
|
3343
|
+
}
|
|
3344
|
+
function average(values) {
|
|
3345
|
+
return values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : 0;
|
|
3346
|
+
}
|
|
3347
|
+
function trendDeltaLabel(delta, formatter) {
|
|
3348
|
+
if (!delta)
|
|
3349
|
+
return 'flat';
|
|
3350
|
+
if (formatter === formatPercent)
|
|
3351
|
+
return `${delta > 0 ? '+' : ''}${Math.round(delta * 100)}pp`;
|
|
3352
|
+
const prefix = delta > 0 ? '+' : '';
|
|
3353
|
+
return `${prefix}${formatter(delta)}`;
|
|
3354
|
+
}
|
|
3355
|
+
function firstPositiveNumber(...values) {
|
|
3356
|
+
for (const value of values) {
|
|
3357
|
+
const number = numberValue(value);
|
|
3358
|
+
if (number > 0)
|
|
3359
|
+
return number;
|
|
3360
|
+
}
|
|
3361
|
+
return 0;
|
|
3362
|
+
}
|
|
3363
|
+
const OPENAI_STANDARD_PRICING_SOURCE = 'OpenAI standard pricing';
|
|
3364
|
+
const MODEL_PRICES_PER_MILLION = [
|
|
3365
|
+
{ id: 'gpt-5.5-pro', price: { input: 30, cachedInput: 30, output: 180 }, longContextPrice: { input: 60, cachedInput: 60, output: 270 }, source: OPENAI_STANDARD_PRICING_SOURCE, notes: 'no cached-input discount listed' },
|
|
3366
|
+
{ id: 'gpt-5.5', price: { input: 5, cachedInput: 0.5, output: 30 }, longContextThreshold: 272_000, longContextPrice: { input: 10, cachedInput: 1, output: 45 }, source: OPENAI_STANDARD_PRICING_SOURCE },
|
|
3367
|
+
{ id: 'gpt-5.4-pro', price: { input: 30, cachedInput: 30, output: 180 }, longContextPrice: { input: 60, cachedInput: 60, output: 270 }, source: OPENAI_STANDARD_PRICING_SOURCE, notes: 'no cached-input discount listed' },
|
|
3368
|
+
{ id: 'gpt-5.4-mini', price: { input: 0.75, cachedInput: 0.075, output: 4.5 }, source: OPENAI_STANDARD_PRICING_SOURCE },
|
|
3369
|
+
{ id: 'gpt-5.4-nano', price: { input: 0.2, cachedInput: 0.02, output: 1.25 }, source: OPENAI_STANDARD_PRICING_SOURCE },
|
|
3370
|
+
{ id: 'gpt-5.4', price: { input: 2.5, cachedInput: 0.25, output: 15 }, longContextPrice: { input: 5, cachedInput: 0.5, output: 22.5 }, source: OPENAI_STANDARD_PRICING_SOURCE },
|
|
3371
|
+
{ id: 'gpt-5.3-codex', aliases: ['gpt-5.3-codex-spark'], price: { input: 1.75, cachedInput: 0.175, output: 14 }, source: OPENAI_STANDARD_PRICING_SOURCE },
|
|
3372
|
+
{ id: 'gpt-5.2', price: { input: 1.75, cachedInput: 0.175, output: 14 }, source: OPENAI_STANDARD_PRICING_SOURCE },
|
|
3373
|
+
{ id: 'gpt-5.1-codex-max', price: { input: 1.25, cachedInput: 0.125, output: 10 }, source: OPENAI_STANDARD_PRICING_SOURCE },
|
|
3374
|
+
{ id: 'gpt-5.1-codex-mini', price: { input: 0.25, cachedInput: 0.025, output: 2 }, source: OPENAI_STANDARD_PRICING_SOURCE },
|
|
3375
|
+
{ id: 'gpt-5-codex', price: { input: 1.25, cachedInput: 0.125, output: 10 }, source: OPENAI_STANDARD_PRICING_SOURCE },
|
|
3376
|
+
{ id: 'gpt-5-chat-latest', aliases: ['chat-latest'], price: { input: 5, cachedInput: 0.5, output: 30 }, source: OPENAI_STANDARD_PRICING_SOURCE },
|
|
3377
|
+
{ id: 'gpt-5-mini', price: { input: 0.25, cachedInput: 0.025, output: 2 }, source: OPENAI_STANDARD_PRICING_SOURCE },
|
|
3378
|
+
{ id: 'gpt-5-nano', price: { input: 0.05, cachedInput: 0.005, output: 0.4 }, source: OPENAI_STANDARD_PRICING_SOURCE },
|
|
3379
|
+
{ id: 'gpt-5', price: { input: 1.25, cachedInput: 0.125, output: 10 }, source: OPENAI_STANDARD_PRICING_SOURCE },
|
|
3380
|
+
{ id: 'gpt-4.1-mini', price: { input: 0.4, cachedInput: 0.1, output: 1.6 }, source: OPENAI_STANDARD_PRICING_SOURCE },
|
|
3381
|
+
{ id: 'gpt-4.1-nano', price: { input: 0.1, cachedInput: 0.025, output: 0.4 }, source: OPENAI_STANDARD_PRICING_SOURCE },
|
|
3382
|
+
{ id: 'gpt-4.1', price: { input: 2, cachedInput: 0.5, output: 8 }, source: OPENAI_STANDARD_PRICING_SOURCE },
|
|
3383
|
+
{ id: 'o4-mini-deep-research', price: { input: 1, cachedInput: 1, output: 4 }, source: OPENAI_STANDARD_PRICING_SOURCE, notes: 'cached-input discount not listed for deep research' },
|
|
3384
|
+
{ id: 'o4-mini', price: { input: 1.1, cachedInput: 0.275, output: 4.4 }, source: OPENAI_STANDARD_PRICING_SOURCE }
|
|
3385
|
+
];
|
|
3386
|
+
function modelCostSummary(jobs) {
|
|
3387
|
+
const allCosts = jobs.map(modelCostForJob);
|
|
3388
|
+
const costs = allCosts.filter((entry) => entry.price);
|
|
3389
|
+
const unpricedCount = allCosts.length - costs.length;
|
|
3390
|
+
const total = costs.reduce((sum, entry) => sum + entry.cost, 0);
|
|
3391
|
+
if (!costs.length)
|
|
3392
|
+
return { value: '-', detail: 'no priced model labels in this run' };
|
|
3393
|
+
const billableTokenJobs = costs.filter((entry) => entry.uncachedInputTokens > 0 || entry.cachedInputTokens > 0 || entry.outputTokens > 0).length;
|
|
3394
|
+
const models = uniqueStrings(costs.map((entry) => entry.model));
|
|
3395
|
+
const outputJobs = costs.filter((entry) => entry.outputTokens > 0).length;
|
|
3396
|
+
const estimated = costs.filter((entry) => entry.estimatedInput).length;
|
|
3397
|
+
const longContextJobs = costs.filter((entry) => entry.longContext).length;
|
|
3398
|
+
const pricingSources = uniqueStrings(costs.map((entry) => entry.pricingSource).filter((value) => Boolean(value)));
|
|
3399
|
+
const modelText = models.length > 3 ? `${models.slice(0, 3).join(', ')} +${models.length - 3}` : models.join(', ');
|
|
3400
|
+
const sourceText = pricingSources.length ? ` · ${pricingSources.join(', ')}` : '';
|
|
3401
|
+
const unpricedText = unpricedCount ? ` · ${formatNumber(unpricedCount)} unpriced jobs` : '';
|
|
3402
|
+
if (!billableTokenJobs || total <= 0) {
|
|
3403
|
+
return { value: 'unknown', detail: `priced models found, awaiting billable token counts · ${modelText}${sourceText}${unpricedText}` };
|
|
3404
|
+
}
|
|
3405
|
+
const scope = outputJobs === 0 ? 'input-only' : outputJobs === costs.length ? 'input+output' : 'input+partial output';
|
|
3406
|
+
const estimatedText = estimated ? ` · ${formatNumber(estimated)} estimated-input jobs` : '';
|
|
3407
|
+
const longContextText = longContextJobs ? ` · ${formatNumber(longContextJobs)} long-context priced` : '';
|
|
3408
|
+
const missingOutputText = outputJobs > 0 && outputJobs < costs.length ? ` · ${formatNumber(costs.length - outputJobs)} without output tokens` : '';
|
|
3409
|
+
const noOutputText = outputJobs === 0 ? ' · no output tokens reported' : '';
|
|
3410
|
+
return { value: formatUsd(total), detail: `${scope} estimate · ${modelText}${sourceText}${longContextText}${estimatedText}${missingOutputText}${noOutputText}${unpricedText}` };
|
|
3411
|
+
}
|
|
3412
|
+
function modelCostRows(jobs) {
|
|
3413
|
+
return jobs
|
|
3414
|
+
.map((job) => ({ job, cost: modelCostForJob(job) }))
|
|
3415
|
+
.filter((entry) => entry.cost.price && entry.cost.cost > 0)
|
|
3416
|
+
.sort((left, right) => right.cost.cost - left.cost.cost)
|
|
3417
|
+
.slice(0, 5)
|
|
3418
|
+
.map(({ job, cost }) => ({
|
|
3419
|
+
label: `${ticketId(job)} · ${taskTitle(job)}`,
|
|
3420
|
+
value: formatUsd(cost.cost),
|
|
3421
|
+
detail: `${cost.model} estimate · ${formatNumber(cost.uncachedInputTokens)} uncached · ${formatNumber(cost.cachedInputTokens)} cached${cost.outputTokens ? ` · ${formatNumber(cost.outputTokens)} output` : ''}${cost.longContext ? ' · long context' : ''}${cost.estimatedInput ? ' · estimated input' : ''}`
|
|
3422
|
+
}));
|
|
3423
|
+
}
|
|
3424
|
+
function modelCostForJob(job) {
|
|
3425
|
+
const model = agentModelLabel(job);
|
|
3426
|
+
const priceEntry = modelPriceEntryForModel(model);
|
|
3427
|
+
const input = inputTokenBreakdownForJob(job);
|
|
3428
|
+
const outputTokens = jobOutputTokens(job);
|
|
3429
|
+
const longContext = Boolean(priceEntry?.longContextPrice && priceEntry.longContextThreshold && input.inputTokens > priceEntry.longContextThreshold);
|
|
3430
|
+
const price = priceEntry ? (longContext ? priceEntry.longContextPrice ?? priceEntry.price : priceEntry.price) : undefined;
|
|
3431
|
+
if (!price)
|
|
3432
|
+
return { model, ...input, outputTokens, longContext: false, cost: 0 };
|
|
3433
|
+
const cost = ((input.uncachedInputTokens * price.input) + (input.cachedInputTokens * price.cachedInput) + (outputTokens * price.output)) / 1_000_000;
|
|
3434
|
+
return { model, price, pricingSource: priceEntry?.source, ...input, outputTokens, longContext, cost };
|
|
3435
|
+
}
|
|
3436
|
+
function modelPriceEntryForModel(model) {
|
|
3437
|
+
const key = normalizedModelKey(model);
|
|
3438
|
+
return MODEL_PRICES_PER_MILLION.find((entry) => modelPriceEntryMatches(key, entry));
|
|
3439
|
+
}
|
|
3440
|
+
function modelPriceEntryMatches(key, entry) {
|
|
3441
|
+
return [entry.id, ...(entry.aliases ?? [])].some((candidate) => {
|
|
3442
|
+
const normalizedCandidate = normalizedModelKey(candidate);
|
|
3443
|
+
return key === normalizedCandidate || isDatedModelVariant(key, normalizedCandidate);
|
|
3444
|
+
});
|
|
3445
|
+
}
|
|
3446
|
+
function isDatedModelVariant(key, base) {
|
|
3447
|
+
if (!key.startsWith(`${base}-`))
|
|
3448
|
+
return false;
|
|
3449
|
+
const suffix = key.slice(base.length + 1);
|
|
3450
|
+
return /^\d{4}-\d{2}-\d{2}(?:$|-)/.test(suffix);
|
|
3451
|
+
}
|
|
3452
|
+
function normalizedModelKey(value) {
|
|
3453
|
+
const raw = value.toLowerCase().trim();
|
|
3454
|
+
const pathKey = raw.includes('/') ? raw.split('/').pop() ?? raw : raw;
|
|
3455
|
+
return pathKey.includes(':') ? pathKey.split(':').pop() ?? pathKey : pathKey;
|
|
3456
|
+
}
|
|
3457
|
+
function inputTokenBreakdownForJob(job) {
|
|
3458
|
+
const usage = usageRecord(job);
|
|
3459
|
+
const inputDetails = recordValue(job.inputTokensDetails ?? job.input_tokens_details ?? usage.input_tokens_details);
|
|
3460
|
+
const promptDetails = recordValue(job.promptTokensDetails ?? job.prompt_tokens_details ?? usage.prompt_tokens_details);
|
|
3461
|
+
const actualInput = firstPositiveNumber(job.actualInputTokens, job.inputTokens, job.promptTokens, job.actual_input_tokens, job.input_tokens, job.prompt_tokens, usage.input_tokens, usage.prompt_tokens);
|
|
3462
|
+
const estimatedInput = firstPositiveNumber(job.estimatedInputTokens, job.estimated_input_tokens);
|
|
3463
|
+
const reportedCached = firstPositiveNumber(job.cachedInputTokens, job.cachedPromptTokens, job.cached_input_tokens, job.cached_prompt_tokens, inputDetails.cached_tokens, promptDetails.cached_tokens, usage.cached_input_tokens, usage.cached_prompt_tokens, usage.cached_tokens);
|
|
3464
|
+
const reportedUncached = firstPositiveNumber(job.uncachedInputTokens, job.uncached_input_tokens, usage.uncached_input_tokens);
|
|
3465
|
+
const inputTokens = actualInput || estimatedInput || reportedCached + reportedUncached;
|
|
3466
|
+
if (!actualInput) {
|
|
3467
|
+
return {
|
|
3468
|
+
inputTokens,
|
|
3469
|
+
cachedInputTokens: 0,
|
|
3470
|
+
uncachedInputTokens: inputTokens,
|
|
3471
|
+
estimatedInput: inputTokens > 0
|
|
3472
|
+
};
|
|
3473
|
+
}
|
|
3474
|
+
let cachedInputTokens = reportedCached ? Math.min(actualInput, reportedCached) : 0;
|
|
3475
|
+
let uncachedInputTokens = reportedUncached ? Math.min(actualInput, reportedUncached) : Math.max(0, actualInput - cachedInputTokens);
|
|
3476
|
+
if (reportedUncached && !reportedCached)
|
|
3477
|
+
cachedInputTokens = Math.max(0, actualInput - uncachedInputTokens);
|
|
3478
|
+
if (cachedInputTokens + uncachedInputTokens > actualInput) {
|
|
3479
|
+
uncachedInputTokens = Math.min(actualInput, uncachedInputTokens);
|
|
3480
|
+
cachedInputTokens = Math.max(0, actualInput - uncachedInputTokens);
|
|
3481
|
+
}
|
|
3482
|
+
if (cachedInputTokens + uncachedInputTokens < actualInput) {
|
|
3483
|
+
uncachedInputTokens += actualInput - cachedInputTokens - uncachedInputTokens;
|
|
3484
|
+
}
|
|
3485
|
+
return {
|
|
3486
|
+
inputTokens: actualInput,
|
|
3487
|
+
cachedInputTokens,
|
|
3488
|
+
uncachedInputTokens,
|
|
3489
|
+
estimatedInput: false
|
|
3490
|
+
};
|
|
3491
|
+
}
|
|
3492
|
+
function usageRecord(job) {
|
|
3493
|
+
return recordValue(job.usage ?? job.tokenUsage ?? job.openaiUsage ?? job.openAIUsage);
|
|
3494
|
+
}
|
|
3495
|
+
function jobOutputTokens(job) {
|
|
3496
|
+
const usage = usageRecord(job);
|
|
3497
|
+
return firstPositiveNumber(job.actualOutputTokens, job.outputTokens, job.completionTokens, job.responseTokens, job.generatedTokens, job.actual_output_tokens, job.output_tokens, job.completion_tokens, job.response_tokens, job.generated_tokens, usage.output_tokens, usage.completion_tokens, usage.response_tokens, usage.generated_tokens);
|
|
3498
|
+
}
|
|
3499
|
+
function timeSeriesPoints(dashboard) {
|
|
3500
|
+
const points = dashboard.timeSeries?.points;
|
|
3501
|
+
return Array.isArray(points) ? points : [];
|
|
3502
|
+
}
|
|
3503
|
+
function timeSeriesPoint(point, key, label, tone = 'neutral', formatter = formatNumber) {
|
|
3504
|
+
const value = numberValue(point[key]);
|
|
3505
|
+
return {
|
|
3506
|
+
label: formatTime(point.at),
|
|
3507
|
+
value,
|
|
3508
|
+
detail: `${formatter(value)} ${label}`,
|
|
3509
|
+
tone
|
|
3510
|
+
};
|
|
3511
|
+
}
|
|
3512
|
+
function contextPressureLabel(jobs) {
|
|
3513
|
+
const summary = contextPressureSummary(jobs);
|
|
3514
|
+
if (summary.failedCount)
|
|
3515
|
+
return `${text(summary.failedCount)} failed budget`;
|
|
3516
|
+
if (summary.warningCount)
|
|
3517
|
+
return `${text(summary.warningCount)} warnings`;
|
|
3518
|
+
if (summary.uncachedInputTokens)
|
|
3519
|
+
return `${formatRatio(summary.uncachedRatio)} uncached/estimated`;
|
|
3520
|
+
return 'no pressure';
|
|
3521
|
+
}
|
|
3522
|
+
function contextPressureSummary(jobs) {
|
|
3523
|
+
const inputBreakdowns = jobs.map(inputTokenBreakdownForJob);
|
|
3524
|
+
const actualValues = inputBreakdowns
|
|
3525
|
+
.filter((input) => !input.estimatedInput)
|
|
3526
|
+
.map((input) => input.inputTokens)
|
|
3527
|
+
.filter((value) => value > 0)
|
|
3528
|
+
.sort((left, right) => left - right);
|
|
3529
|
+
const uncachedValues = inputBreakdowns
|
|
3530
|
+
.map((input) => input.uncachedInputTokens)
|
|
3531
|
+
.filter((value) => value > 0)
|
|
3532
|
+
.sort((left, right) => left - right);
|
|
3533
|
+
const estimatedInputTokens = inputBreakdowns.reduce((sum, input) => sum + (input.estimatedInput ? input.inputTokens : 0), 0);
|
|
3534
|
+
const actualInputTokens = inputBreakdowns.reduce((sum, input) => sum + (input.estimatedInput ? 0 : input.inputTokens), 0);
|
|
3535
|
+
const cachedInputTokens = inputBreakdowns.reduce((sum, input) => sum + input.cachedInputTokens, 0);
|
|
3536
|
+
const uncachedInputTokens = inputBreakdowns.reduce((sum, input) => sum + input.uncachedInputTokens, 0);
|
|
3537
|
+
return {
|
|
3538
|
+
warningCount: jobs.filter(isContextBudgetWarningJob).length,
|
|
3539
|
+
failedCount: jobs.filter(isContextBudgetFailedJob).length,
|
|
3540
|
+
estimatedInputTokens,
|
|
3541
|
+
actualInputTokens,
|
|
3542
|
+
cachedInputTokens,
|
|
3543
|
+
uncachedInputTokens,
|
|
3544
|
+
p95ActualInputTokens: percentileValue(actualValues, 0.95),
|
|
3545
|
+
p95UncachedInputTokens: percentileValue(uncachedValues, 0.95),
|
|
3546
|
+
ratio: estimatedInputTokens > 0 && actualInputTokens > 0 ? actualInputTokens / estimatedInputTokens : 0,
|
|
3547
|
+
uncachedRatio: estimatedInputTokens > 0 && uncachedInputTokens > 0 ? uncachedInputTokens / estimatedInputTokens : 0,
|
|
3548
|
+
cacheHitRatio: actualInputTokens > 0 && cachedInputTokens > 0 ? cachedInputTokens / actualInputTokens : 0
|
|
3549
|
+
};
|
|
3550
|
+
}
|
|
3551
|
+
function contextOffenderRows(jobs) {
|
|
3552
|
+
return jobs
|
|
3553
|
+
.map((job) => {
|
|
3554
|
+
const input = inputTokenBreakdownForJob(job);
|
|
3555
|
+
const actualInputTokens = input.estimatedInput ? 0 : input.inputTokens;
|
|
3556
|
+
const uncachedInputTokens = input.uncachedInputTokens;
|
|
3557
|
+
const estimatedInputTokens = input.estimatedInput ? input.inputTokens : 0;
|
|
3558
|
+
const warning = isContextBudgetWarningJob(job);
|
|
3559
|
+
const failed = isContextBudgetFailedJob(job);
|
|
3560
|
+
return {
|
|
3561
|
+
id: textValue(job.id ?? job.taskId ?? job.title, 'job'),
|
|
3562
|
+
label: `${ticketId(job)} · ${taskTitle(job)}`,
|
|
3563
|
+
lane: laneOf(job),
|
|
3564
|
+
sourceLabel: textValue(job.sourceLabel, ''),
|
|
3565
|
+
actualInputTokens,
|
|
3566
|
+
uncachedInputTokens,
|
|
3567
|
+
estimatedInputTokens,
|
|
3568
|
+
statusLabel: failed ? 'budget failed' : warning ? 'budget warning' : 'reported usage'
|
|
3569
|
+
};
|
|
3570
|
+
})
|
|
3571
|
+
.filter((job) => job.actualInputTokens > 0 || job.estimatedInputTokens > 0)
|
|
3572
|
+
.sort((left, right) => (right.uncachedInputTokens || right.actualInputTokens || right.estimatedInputTokens) - (left.uncachedInputTokens || left.actualInputTokens || left.estimatedInputTokens))
|
|
3573
|
+
.slice(0, 5);
|
|
3574
|
+
}
|
|
3575
|
+
function contextDriverDetail(job) {
|
|
3576
|
+
return [job.lane, job.sourceLabel, job.statusLabel].filter(Boolean).join(' · ');
|
|
3577
|
+
}
|
|
3578
|
+
function uncachedInputTokensForJob(job) {
|
|
3579
|
+
return inputTokenBreakdownForJob(job).uncachedInputTokens;
|
|
3580
|
+
}
|
|
3581
|
+
function isContextBudgetWarningJob(job) {
|
|
3582
|
+
return numberValue(job.contextBudgetWarningCount) > 0
|
|
3583
|
+
|| normalized(job.contextBudgetStatus) === 'warning';
|
|
3584
|
+
}
|
|
3585
|
+
function isContextBudgetFailedJob(job) {
|
|
3586
|
+
return numberValue(job.contextBudgetErrorCount) > 0
|
|
3587
|
+
|| normalized(job.contextBudgetStatus) === 'failed';
|
|
3588
|
+
}
|
|
3589
|
+
function percentileValue(values, percentile) {
|
|
3590
|
+
if (!values.length)
|
|
3591
|
+
return 0;
|
|
3592
|
+
const index = Math.min(values.length - 1, Math.max(0, Math.ceil(values.length * percentile) - 1));
|
|
3593
|
+
return values[index] ?? 0;
|
|
3594
|
+
}
|
|
3595
|
+
function failureOwnershipRows(attention, audit) {
|
|
3596
|
+
return [
|
|
3597
|
+
{ label: 'Failed jobs', value: attention.failedCount, tone: 'bad' },
|
|
3598
|
+
{ label: 'Coordinator review', value: attention.needsCoordinatorReviewCount, tone: 'review' },
|
|
3599
|
+
{ label: 'Stale jobs', value: attention.staleCount, tone: 'warn' },
|
|
3600
|
+
{ label: 'Source violations', value: audit.sourceOwnershipViolationCount, tone: audit.sourceOwnershipViolationCount ? 'bad' : 'neutral' },
|
|
3601
|
+
{ label: 'Generated noise', value: generatedNoiseCount(audit), tone: generatedNoiseCount(audit) ? 'warn' : 'neutral' },
|
|
3602
|
+
{ label: 'Quarantined', value: audit.quarantinedChangedPathCount, tone: audit.quarantinedChangedPathCount ? 'warn' : 'neutral' }
|
|
3603
|
+
];
|
|
3604
|
+
}
|
|
3605
|
+
function eventBucketPoints(events, predicate, tone = 'neutral') {
|
|
3606
|
+
const count = 12;
|
|
3607
|
+
const buckets = Array.from({ length: count }, () => 0);
|
|
3608
|
+
const times = events
|
|
3609
|
+
.map((event) => Number(event.at))
|
|
3610
|
+
.filter((value) => Number.isFinite(value) && value > 0);
|
|
3611
|
+
const hasTimes = times.length > 0;
|
|
3612
|
+
const min = hasTimes ? Math.min(...times) : 0;
|
|
3613
|
+
const max = hasTimes ? Math.max(...times) : 0;
|
|
3614
|
+
const span = Math.max(1, max - min);
|
|
3615
|
+
events.forEach((event, index) => {
|
|
3616
|
+
if (!predicate(event))
|
|
3617
|
+
return;
|
|
3618
|
+
const at = Number(event.at);
|
|
3619
|
+
const bucket = hasTimes && Number.isFinite(at) && at > 0
|
|
3620
|
+
? Math.min(count - 1, Math.max(0, Math.floor(((at - min) / span) * (count - 1))))
|
|
3621
|
+
: Math.min(count - 1, Math.floor((index / Math.max(1, events.length)) * count));
|
|
3622
|
+
buckets[bucket] += 1;
|
|
3623
|
+
});
|
|
3624
|
+
return buckets.map((value, index) => ({
|
|
3625
|
+
label: hasTimes ? formatTime(min + Math.round((span * index) / Math.max(1, count - 1))) : `Bucket ${index + 1}`,
|
|
3626
|
+
value,
|
|
3627
|
+
detail: `${formatNumber(value)} events`,
|
|
3628
|
+
tone
|
|
3629
|
+
}));
|
|
3630
|
+
}
|
|
3631
|
+
function jobValuePoints(jobs, key, formatter) {
|
|
3632
|
+
return jobs.slice(-18).map((job) => {
|
|
3633
|
+
const value = numberValue(job[key]);
|
|
3634
|
+
return {
|
|
3635
|
+
label: textValue(job.id ?? job.taskId ?? job.title, 'job'),
|
|
3636
|
+
value,
|
|
3637
|
+
detail: formatter(value)
|
|
3638
|
+
};
|
|
3639
|
+
});
|
|
3640
|
+
}
|
|
3641
|
+
function isProgressEvent(event) {
|
|
3642
|
+
const value = `${textValue(event.type, '')} ${textValue(event.message, '')}`.toLowerCase();
|
|
3643
|
+
return value.includes('complete')
|
|
3644
|
+
|| value.includes('ready')
|
|
3645
|
+
|| value.includes('accepted')
|
|
3646
|
+
|| value.includes('applied')
|
|
3647
|
+
|| value.includes('success');
|
|
3648
|
+
}
|
|
3649
|
+
function isAttentionEvent(event) {
|
|
3650
|
+
const value = `${textValue(event.type, '')} ${textValue(event.message, '')}`.toLowerCase();
|
|
3651
|
+
return value.includes('fail')
|
|
3652
|
+
|| value.includes('error')
|
|
3653
|
+
|| value.includes('reject')
|
|
3654
|
+
|| value.includes('blocked')
|
|
3655
|
+
|| value.includes('stale')
|
|
3656
|
+
|| value.includes('needs');
|
|
3657
|
+
}
|
|
3658
|
+
function formatNumber(value) {
|
|
3659
|
+
return new Intl.NumberFormat('en-US').format(Math.round(value));
|
|
3660
|
+
}
|
|
3661
|
+
function formatUsd(value) {
|
|
3662
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
3663
|
+
return '$0.00';
|
|
3664
|
+
if (value < 0.01)
|
|
3665
|
+
return `$${value.toFixed(4)}`;
|
|
3666
|
+
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: value < 10 ? 2 : 0 }).format(value);
|
|
3667
|
+
}
|
|
3668
|
+
function formatBytes(value) {
|
|
3669
|
+
if (!value)
|
|
3670
|
+
return '0 B';
|
|
3671
|
+
const units = ['B', 'KiB', 'MiB', 'GiB'];
|
|
3672
|
+
let size = value;
|
|
3673
|
+
let unit = 0;
|
|
3674
|
+
while (size >= 1024 && unit < units.length - 1) {
|
|
3675
|
+
size /= 1024;
|
|
3676
|
+
unit += 1;
|
|
3677
|
+
}
|
|
3678
|
+
return `${size >= 10 || unit === 0 ? Math.round(size) : size.toFixed(1)} ${units[unit]}`;
|
|
3679
|
+
}
|
|
3680
|
+
function formatRatio(value) {
|
|
3681
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
3682
|
+
return '-';
|
|
3683
|
+
return `${value >= 10 ? Math.round(value) : value.toFixed(1)}x`;
|
|
3684
|
+
}
|
|
3685
|
+
function formatPercent(value) {
|
|
3686
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
3687
|
+
return '0%';
|
|
3688
|
+
const percent = value * 100;
|
|
3689
|
+
if (value < 1 && percent > 99)
|
|
3690
|
+
return `${floorToDecimal(percent, 1)}%`;
|
|
3691
|
+
if (Math.abs(percent - Math.round(percent)) >= 0.25)
|
|
3692
|
+
return `${percent.toFixed(1)}%`;
|
|
3693
|
+
return `${Math.round(percent)}%`;
|
|
3694
|
+
}
|
|
3695
|
+
function percentNumber(value) {
|
|
3696
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
3697
|
+
return 0;
|
|
3698
|
+
const percent = value * 100;
|
|
3699
|
+
if (value < 1 && percent > 99)
|
|
3700
|
+
return floorToDecimal(percent, 1);
|
|
3701
|
+
return Math.round(percent * 10) / 10;
|
|
3702
|
+
}
|
|
3703
|
+
function floorToDecimal(value, digits) {
|
|
3704
|
+
const scale = 10 ** digits;
|
|
3705
|
+
return Math.floor(value * scale) / scale;
|
|
3706
|
+
}
|
|
3707
|
+
function formatDuration(value) {
|
|
3708
|
+
if (!value)
|
|
3709
|
+
return '0 ms';
|
|
3710
|
+
if (value < 1000)
|
|
3711
|
+
return `${Math.round(value)} ms`;
|
|
3712
|
+
const seconds = value / 1000;
|
|
3713
|
+
if (seconds < 60)
|
|
3714
|
+
return `${seconds >= 10 ? Math.round(seconds) : seconds.toFixed(1)} s`;
|
|
3715
|
+
const minutes = seconds / 60;
|
|
3716
|
+
return `${minutes >= 10 ? Math.round(minutes) : minutes.toFixed(1)} min`;
|
|
3717
|
+
}
|
|
3718
|
+
function formatBucketSize(value) {
|
|
3719
|
+
const ms = numberValue(value);
|
|
3720
|
+
if (!ms)
|
|
3721
|
+
return 'time';
|
|
3722
|
+
if (ms < 1000)
|
|
3723
|
+
return `${ms} ms`;
|
|
3724
|
+
if (ms < 60000)
|
|
3725
|
+
return `${Math.round(ms / 1000)} s`;
|
|
3726
|
+
return `${Math.round(ms / 60000)} min`;
|
|
3727
|
+
}
|
|
3728
|
+
function semanticMetrics(value) {
|
|
3729
|
+
const input = recordValue(value);
|
|
3730
|
+
const imports = recordValue(input.import);
|
|
3731
|
+
const edit = recordValue(input.edit);
|
|
3732
|
+
const script = recordValue(edit.script);
|
|
3733
|
+
const replay = recordValue(input.replay);
|
|
3734
|
+
const admission = recordValue(input.admission);
|
|
3735
|
+
const admissionRows = [
|
|
3736
|
+
...semanticAdmissionRows(admission.jobs, 'Jobs'),
|
|
3737
|
+
...semanticAdmissionRows(admission.scripts, 'Scripts')
|
|
3738
|
+
];
|
|
3739
|
+
const metrics = {
|
|
3740
|
+
expected: numberValue(imports.expectedCount),
|
|
3741
|
+
satisfied: numberValue(imports.expectedSatisfiedCount),
|
|
3742
|
+
candidates: numberValue(imports.candidateCount),
|
|
3743
|
+
autoMerge: numberValue(script.autoMergeCandidateCount),
|
|
3744
|
+
acceptedClean: numberValue(replay.acceptedCleanCount),
|
|
3745
|
+
conflicts: numberValue(replay.conflictCount)
|
|
3746
|
+
};
|
|
3747
|
+
return {
|
|
3748
|
+
...metrics,
|
|
3749
|
+
total: metrics.expected + metrics.satisfied + metrics.candidates + metrics.autoMerge + metrics.acceptedClean + metrics.conflicts,
|
|
3750
|
+
admissionRows,
|
|
3751
|
+
admissionTotal: admissionRows.reduce((sum, row) => sum + row.value, 0)
|
|
3752
|
+
};
|
|
3753
|
+
}
|
|
3754
|
+
function semanticSuccessRows(value) {
|
|
3755
|
+
const input = recordValue(value);
|
|
3756
|
+
const imports = recordValue(input.import);
|
|
3757
|
+
const edit = recordValue(input.edit);
|
|
3758
|
+
const script = recordValue(edit.script);
|
|
3759
|
+
const projection = recordValue(edit.projection);
|
|
3760
|
+
const replay = recordValue(input.replay);
|
|
3761
|
+
const rows = [
|
|
3762
|
+
{ label: 'Imports satisfied', value: numberValue(imports.expectedSatisfiedCount), tone: 'good' },
|
|
3763
|
+
{ label: 'Auto-merge candidates', value: numberValue(script.autoMergeCandidateCount), tone: 'good' },
|
|
3764
|
+
{ label: 'Portable edit scripts', value: numberValue(script.portableCount), tone: 'good' },
|
|
3765
|
+
{ label: 'Projected edits', value: numberValue(projection.appliedEditCount), tone: 'good' },
|
|
3766
|
+
{ label: 'Replay accepted clean', value: numberValue(replay.acceptedCleanCount), tone: 'good' },
|
|
3767
|
+
{ label: 'Already applied', value: numberValue(replay.alreadyAppliedCount), tone: 'good' }
|
|
3768
|
+
];
|
|
3769
|
+
return rows.filter((row) => row.value > 0);
|
|
3770
|
+
}
|
|
3771
|
+
function semanticAdmissionRows(value, prefix) {
|
|
3772
|
+
const input = recordValue(value);
|
|
3773
|
+
const statusCounts = recordValue(input.statusCounts);
|
|
3774
|
+
const rows = Object.entries(statusCounts)
|
|
3775
|
+
.map(([label, count]) => ({
|
|
3776
|
+
label: `${prefix} ${label}`,
|
|
3777
|
+
value: numberValue(count),
|
|
3778
|
+
tone: admissionTone(label)
|
|
3779
|
+
}))
|
|
3780
|
+
.filter((row) => row.value > 0);
|
|
3781
|
+
for (const [key, label] of [
|
|
3782
|
+
['autoMergeCandidateCount', 'auto-merge'],
|
|
3783
|
+
['cleanEligibleCount', 'clean eligible'],
|
|
3784
|
+
['portableCount', 'portable'],
|
|
3785
|
+
['cleanEligibleCandidateCount', 'clean candidates']
|
|
3786
|
+
]) {
|
|
3787
|
+
const value = numberValue(input[key]);
|
|
3788
|
+
if (value > 0)
|
|
3789
|
+
rows.push({ label: `${prefix} ${label}`, value, tone: admissionTone(label) });
|
|
3790
|
+
}
|
|
3791
|
+
return rows;
|
|
3792
|
+
}
|
|
3793
|
+
function admissionTone(value) {
|
|
3794
|
+
const label = value.toLowerCase();
|
|
3795
|
+
if (label.includes('fail') || label.includes('reject') || label.includes('conflict') || label.includes('blocked') || label.includes('stale'))
|
|
3796
|
+
return 'bad';
|
|
3797
|
+
if (label.includes('needs') || label.includes('warning'))
|
|
3798
|
+
return 'warn';
|
|
3799
|
+
if (label.includes('clean') || label.includes('auto') || label.includes('portable') || label.includes('accept'))
|
|
3800
|
+
return 'good';
|
|
3801
|
+
return 'neutral';
|
|
3802
|
+
}
|
|
3803
|
+
function recordValue(value) {
|
|
3804
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
3805
|
+
}
|
|
3806
|
+
function arrayRecords(value) {
|
|
3807
|
+
return Array.isArray(value) ? value.map(recordValue).filter((entry) => Object.keys(entry).length > 0) : [];
|
|
3808
|
+
}
|
|
3809
|
+
function stringArray(value) {
|
|
3810
|
+
return Array.isArray(value) ? value.map(String).filter(Boolean) : [];
|
|
3811
|
+
}
|
|
3812
|
+
function normalized(value) {
|
|
3813
|
+
return textValue(value, '').toLowerCase();
|
|
3814
|
+
}
|
|
3815
|
+
function formatTime(value) {
|
|
3816
|
+
const number = Number(value);
|
|
3817
|
+
if (!Number.isFinite(number) || number <= 0)
|
|
3818
|
+
return '-';
|
|
3819
|
+
return new Date(number).toISOString().replace('T', ' ').replace('.000Z', 'Z');
|
|
3820
|
+
}
|
|
3821
|
+
function bucketText(value) {
|
|
3822
|
+
if (!value || typeof value !== 'object')
|
|
3823
|
+
return '-';
|
|
3824
|
+
const counts = value;
|
|
3825
|
+
return `${text(counts['ready-to-apply'])}/${text(counts.total)}`;
|
|
3826
|
+
}
|
|
3827
|
+
//# sourceMappingURL=client.js.map
|