@shapeshift-labs/frontier-loom-ui 0.1.0 → 0.1.2

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/dist/client.js CHANGED
@@ -3,6 +3,7 @@ const contentTabs = [
3
3
  { id: 'work', label: 'Overview' },
4
4
  { id: 'board', label: 'Board' },
5
5
  { id: 'swarm', label: 'Swarm' },
6
+ { id: 'lanes', label: 'Lanes' },
6
7
  { id: 'performance', label: 'Performance' },
7
8
  { id: 'history', label: 'History' },
8
9
  { id: 'testing', label: 'Testing' },
@@ -21,6 +22,7 @@ let chartPopover;
21
22
  let activeContributionTarget;
22
23
  const taskDetailsCache = new Map();
23
24
  const taskDetailsPending = new Set();
25
+ const taskFileDiffOpenStates = new Map();
24
26
  const humanAnswerDrafts = new Map();
25
27
  const humanAnswerStates = new Map();
26
28
  root?.addEventListener('click', (event) => {
@@ -70,6 +72,14 @@ root?.addEventListener('click', (event) => {
70
72
  }
71
73
  return;
72
74
  });
75
+ root?.addEventListener('toggle', (event) => {
76
+ const target = event.target instanceof Element
77
+ ? event.target.closest('details[data-task-file-diff-key]')
78
+ : null;
79
+ if (!target)
80
+ return;
81
+ setTaskFileDiffOpenState(target);
82
+ }, true);
73
83
  root?.addEventListener('input', (event) => {
74
84
  const target = event.target instanceof Element
75
85
  ? event.target.closest('textarea[data-human-answer-code]')
@@ -240,6 +250,7 @@ async function refresh() {
240
250
  function renderDashboard(dashboard, signature = dashboardSignature(dashboard)) {
241
251
  hideChartPopover();
242
252
  captureScrollPositions();
253
+ captureTaskFileDiffOpenStates();
243
254
  pruneResolvedHumanActionDrafts(dashboard);
244
255
  const lanes = laneRollups(dashboard);
245
256
  root?.replaceChildren(_jsx(DashboardView, { dashboard: dashboard, lanes: lanes }));
@@ -277,6 +288,7 @@ function DashboardView({ dashboard, lanes }) {
277
288
  void fetchTaskDetails(selectedTask);
278
289
  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
290
  jobs: visibleJobs.length,
291
+ lanes: dashboardLaneCount(dashboard, lanes),
280
292
  questions: humanActionRows(visibleJobs, audit, dashboard).length,
281
293
  events: visibleEvents.length,
282
294
  sources: sourceEntries.length
@@ -296,12 +308,14 @@ function contentPanel(tab, input) {
296
308
  if (tab === 'work')
297
309
  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
310
  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 }) });
311
+ return _jsx(Panel, { title: "Task board", meta: boardPanelMeta(input.dashboard, boardItems, input.visibleJobs), children: _jsx(TaskBoard, { dashboard: input.dashboard, jobs: input.visibleJobs }) });
300
312
  if (tab === 'swarm') {
301
313
  const sourceKind = dashboardSourceKind(input.dashboard);
302
314
  const activeCount = activeAgentTaskCount(input.dashboard, input.visibleJobs);
303
315
  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
316
  }
317
+ if (tab === 'lanes')
318
+ return _jsx(Panel, { title: "Lanes", meta: `${text(dashboardLaneCount(input.dashboard, input.lanes))} lanes`, children: _jsx(LaneWork, { dashboard: input.dashboard, lanes: input.lanes }) });
305
319
  if (tab === 'performance')
306
320
  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
321
  if (tab === 'history')
@@ -310,6 +324,14 @@ function contentPanel(tab, input) {
310
324
  return _jsx(Panel, { title: "Testing", meta: testingTabMeta(input.visibleJobs), children: _jsx(TestingView, { jobs: input.visibleJobs, events: input.visibleEvents }) });
311
325
  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
326
  }
327
+ function boardPanelMeta(dashboard, items, jobs) {
328
+ const scope = boardScopeLabel(dashboard);
329
+ const hiddenCount = Math.max(0, jobs.length - items.filter((item) => item.boardKind !== 'backlog').length);
330
+ const doneCount = items.filter((item) => taskBoardColumnId(item) === 'done').length;
331
+ if (hiddenCount)
332
+ return `${text(items.length)} ${scope} tasks · ${text(doneCount)} done · ${text(hiddenCount)} hidden resolved noise`;
333
+ return `${text(items.length)} ${scope} tasks · ${text(doneCount)} done`;
334
+ }
313
335
  function Panel({ title, meta, children, className, hideHead = false }) {
314
336
  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
337
  }
@@ -320,15 +342,17 @@ function contentTab(id, label, meta) {
320
342
  function WorkOverview({ dashboard, lanes, jobs, attention, audit, success }) {
321
343
  const health = dashboardHealthSummary(dashboard, jobs, attention);
322
344
  const contribution = contributionGrid(dashboard, jobs, dashboard.events);
323
- const successfulJobCount = successLikeJobCount(jobs);
324
- const progressRatio = health.jobCount ? successfulJobCount / health.jobCount : 0;
345
+ const resolvedWorkCount = resolvedWorkJobCount(jobs);
346
+ const workerSuccessCount = successLikeJobCount(jobs);
347
+ const progressRatio = health.jobCount ? resolvedWorkCount / health.jobCount : 0;
348
+ const workerReliabilityRatio = health.jobCount ? workerSuccessCount / health.jobCount : 0;
325
349
  const progressLabel = formatPercent(progressRatio);
326
350
  const progressWidth = Math.max(0, Math.min(100, progressRatio * 100));
327
351
  const cost = workCostSummary(dashboard, jobs);
328
352
  void lanes;
329
353
  void audit;
330
354
  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)] })] })] });
355
+ 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(jobs, health, resolvedWorkCount) })] }), _jsxs("div", { className: "goal-progress", children: [_jsxs("div", { className: "goal-progress-head", children: [_jsx("b", { children: progressLabel }), _jsxs("span", { children: [text(resolvedWorkCount), " of ", text(health.jobCount), " tasks resolved"] })] }), _jsx("div", { className: "goal-progress-track", role: "img", "aria-label": `${progressLabel} resolved work progress`, children: _jsx("span", { className: `goal-progress-fill ${progressTone(progressRatio, workerReliabilityRatio)}`, style: `width:${progressWidth}%` }) }), _jsxs("small", { className: "goal-reliability", children: ["Worker reliability ", formatPercent(workerReliabilityRatio), " \u00B7 ", text(workerSuccessCount), " clean completions"] })] })] }), _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
356
  }
333
357
  function currentGoalTitle(dashboard) {
334
358
  const backlog = recordValue(dashboard.backlog);
@@ -349,19 +373,28 @@ function sentenceCaseIdentifier(value) {
349
373
  return value;
350
374
  return spaced[0].toUpperCase() + spaced.slice(1);
351
375
  }
352
- function goalProgressText(health, successfulJobCount) {
376
+ function goalProgressText(jobs, health, resolvedWorkCount) {
353
377
  if (!health.jobCount)
354
378
  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.';
379
+ const unresolvedAttentionCount = jobs.filter(isCoordinatorReviewJob).length;
380
+ if (resolvedWorkCount >= health.jobCount)
381
+ return 'Every tracked task has either completed or been resolved by the coordinator.';
358
382
  if (health.runningJobCount) {
359
383
  return `The swarm is working through ${text(health.jobCount)} tracked tasks. ${text(health.runningJobCount)} are still running.`;
360
384
  }
361
385
  if (health.terminalJobCount >= health.jobCount) {
362
- return `Execution has finished, but ${text(attentionCount)} tasks still need review or fixes.`;
386
+ return unresolvedAttentionCount
387
+ ? `Execution has finished, but ${text(unresolvedAttentionCount)} tasks still need coordinator action.`
388
+ : 'Execution has finished and coordinator decisions have collapsed the open review queue.';
363
389
  }
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.`;
390
+ return `No agents are running right now. ${text(Math.max(0, health.jobCount - health.terminalJobCount))} tasks remain queued and ${text(unresolvedAttentionCount)} need coordinator action.`;
391
+ }
392
+ function progressTone(progressRatio, workerReliabilityRatio) {
393
+ if (progressRatio >= 0.8 && workerReliabilityRatio >= 0.5)
394
+ return 'good';
395
+ if (progressRatio >= 0.5)
396
+ return 'warn';
397
+ return 'bad';
365
398
  }
366
399
  function workCostSummary(dashboard, jobs) {
367
400
  const telemetry = tokenTimeSummary(dashboard, jobs);
@@ -383,33 +416,121 @@ function costMetric(label, value, detail) {
383
416
  }
384
417
  function TaskBoard({ dashboard, jobs }) {
385
418
  const items = taskBoardItems(dashboard, jobs);
386
- const columns = taskBoardColumns(items);
419
+ const sourceKind = dashboardSourceKind(dashboard);
420
+ const scope = boardScopeLabel(dashboard);
421
+ const columns = taskBoardColumns(items, sourceKind);
387
422
  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
423
+ 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)} ${scope} 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
424
  ? column.items.map((job) => _jsx(TaskBoardCard, { job: job }))
390
425
  : _jsx("p", { className: "empty tight", children: column.empty }) })] })) }) });
391
426
  }
392
427
  function AgentWork({ dashboard, jobs }) {
393
428
  const items = taskBoardItems(dashboard, jobs).filter(isActiveAgentJob);
394
429
  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." }) })] }) });
430
+ return _jsxs("div", { className: "agent-work-layout", "data-scroll-id": "swarm", children: [_jsx(SwarmCapacityPanel, { dashboard: dashboard, workers: workers }), _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
431
+ ? workers.map((worker, index) => _jsx(AgentWorkerCard, { worker: worker, index: index, now: timeValue(dashboard.generatedAt) ?? Date.now() }))
432
+ : _jsx("p", { className: "agent-roster-empty", children: "No active agents are running right now." }) })] })] });
433
+ }
434
+ function LaneWork({ dashboard, lanes }) {
435
+ const capacity = recordValue(dashboard.capacity);
436
+ const capacityLanes = arrayRecords(capacity.lanes);
437
+ const capacityById = new Map(capacityLanes.map((lane) => [textValue(lane.id, textValue(lane.title, '')), lane]));
438
+ const ids = new Set();
439
+ for (const lane of lanes)
440
+ ids.add(lane.id);
441
+ for (const lane of capacityLanes)
442
+ ids.add(textValue(lane.id, textValue(lane.title, 'lane')));
443
+ const rows = Array.from(ids)
444
+ .filter(Boolean)
445
+ .map((id) => laneDetailRow(id, lanes.find((lane) => lane.id === id), capacityById.get(id)))
446
+ .filter(isActiveLaneRow)
447
+ .sort((left, right) => {
448
+ const group = laneSortGroup(left) - laneSortGroup(right);
449
+ const active = right.runningCount - left.runningCount;
450
+ const queued = right.queuedTaskCount - left.queuedTaskCount;
451
+ const review = (right.needsCoordinatorReviewCount + right.staleCount + right.failedCount) - (left.needsCoordinatorReviewCount + left.staleCount + left.failedCount);
452
+ return group || active || queued || review || left.title.localeCompare(right.title);
453
+ });
454
+ return _jsxs("div", { className: "lane-work-layout", "data-scroll-id": "lanes", children: [_jsx(SwarmCapacityPanel, { dashboard: dashboard, workers: [] }), _jsxs("section", { className: "lane-roster-panel", "aria-label": "Swarm lanes", children: [_jsxs("div", { className: "lane-roster-head", children: [_jsx("span", { children: "Lane" }), _jsx("span", { children: "Work" }), _jsx("span", { children: "Capacity" }), _jsx("span", { children: "Agents" })] }), _jsx("div", { className: "lane-roster-list", "data-scroll-id": "lane-roster", children: rows.length
455
+ ? rows.map((lane) => _jsx(LaneRosterRow, { lane: lane }))
456
+ : _jsx("p", { className: "lane-roster-empty", children: "No lanes have active workers right now." }) })] })] });
457
+ }
458
+ function dashboardLaneCount(dashboard, lanes) {
459
+ const ids = new Set();
460
+ for (const lane of lanes)
461
+ ids.add(lane.id);
462
+ for (const lane of arrayRecords(recordValue(dashboard.capacity).lanes))
463
+ ids.add(textValue(lane.id, textValue(lane.title, '')));
464
+ return Array.from(ids)
465
+ .filter(Boolean)
466
+ .map((id) => laneDetailRow(id, lanes.find((lane) => lane.id === id), arrayRecords(recordValue(dashboard.capacity).lanes).find((lane) => textValue(lane.id, textValue(lane.title, '')) === id)))
467
+ .filter(isActiveLaneRow)
468
+ .length;
469
+ }
470
+ function laneDetailRow(id, rollup, capacity) {
471
+ const row = recordValue(capacity);
472
+ return {
473
+ id,
474
+ title: textValue(row.title, id),
475
+ layer: textValue(row.layer, ''),
476
+ model: textValue(row.model, textValue(row.compute, '')),
477
+ jobCount: rollup?.jobCount ?? numberValue(row.totalTaskCount),
478
+ completedCount: rollup?.completedCount ?? numberValue(row.completedCount),
479
+ runningCount: rollup?.runningCount ?? numberValue(row.runningCount),
480
+ failedCount: rollup?.failedCount ?? numberValue(row.failedCount),
481
+ blockedCount: rollup?.blockedCount ?? numberValue(row.blockedCount),
482
+ queuedTaskCount: numberValue(row.queuedTaskCount),
483
+ needsCoordinatorReviewCount: rollup?.needsCoordinatorReviewCount ?? 0,
484
+ staleCount: rollup?.staleCount ?? 0,
485
+ eventCount: rollup?.eventCount ?? 0,
486
+ maxConcurrency: numberValue(row.maxConcurrency) || 1,
487
+ assignedAgents: stringArray(row.assignedAgents)
488
+ };
489
+ }
490
+ function laneSortGroup(lane) {
491
+ if (lane.runningCount > 0)
492
+ return 0;
493
+ if (lane.queuedTaskCount > 0)
494
+ return 1;
495
+ if (lane.needsCoordinatorReviewCount || lane.staleCount || lane.failedCount || lane.blockedCount)
496
+ return 2;
497
+ if (lane.completedCount || lane.eventCount || lane.jobCount)
498
+ return 3;
499
+ return 4;
500
+ }
501
+ function isActiveLaneRow(lane) {
502
+ return lane.runningCount > 0 || lane.assignedAgents.length > 0;
503
+ }
504
+ function LaneRosterRow({ lane }) {
505
+ return _jsxs("article", { className: "lane-roster-row", children: [_jsxs("div", { className: "lane-cell lane-cell-name", children: [_jsx("b", { children: lane.title }), _jsx("small", { children: [lane.layer || 'lane', lane.model].filter(Boolean).join(' · ') })] }), _jsxs("div", { className: "lane-cell lane-cell-work", children: [_jsxs("b", { children: [text(lane.runningCount), " running \u00B7 ", text(lane.queuedTaskCount), " queued"] }), _jsxs("small", { children: [text(lane.completedCount), " done \u00B7 ", text(lane.failedCount), " failed \u00B7 ", text(lane.eventCount), " events"] })] }), _jsxs("div", { className: "lane-cell lane-cell-capacity", children: [_jsxs("b", { children: [text(lane.runningCount), " / ", text(lane.maxConcurrency)] }), _jsx("small", { children: lane.needsCoordinatorReviewCount ? `${text(lane.needsCoordinatorReviewCount)} review` : lane.staleCount ? `${text(lane.staleCount)} stale` : `${text(lane.jobCount)} tracked` })] }), _jsx("div", { className: "lane-cell lane-cell-agents", children: lane.assignedAgents.length
506
+ ? lane.assignedAgents.map((agent) => _jsx("code", { children: shortAgentId(agent) }))
507
+ : _jsx("span", { children: "idle" }) })] });
508
+ }
509
+ function SwarmCapacityPanel({ dashboard, workers }) {
510
+ const capacity = recordValue(dashboard.capacity);
511
+ const lanes = arrayRecords(capacity.lanes);
512
+ const openLaneCount = numberValue(capacity.openLaneCount);
513
+ const activeLaneCount = numberValue(capacity.activeLaneCount);
514
+ const maxConcurrency = numberValue(capacity.maxConcurrency);
515
+ const runningAgentCount = numberValue(capacity.runningAgentCount) || workers.filter((worker) => worker.status === 'active').length;
516
+ const queuedTaskCount = numberValue(capacity.queuedTaskCount);
517
+ const manifestLabel = textValue(capacity.manifestId, textValue(capacity.title, 'manifest'));
518
+ return _jsx("section", { className: "swarm-capacity-panel", "aria-label": "Swarm capacity", children: _jsxs("div", { className: "swarm-capacity-summary", children: [_jsxs("div", { children: [_jsx("span", { children: "Concurrency" }), _jsx("b", { children: maxConcurrency ? `${text(runningAgentCount)} / ${text(maxConcurrency)}` : text(runningAgentCount) }), _jsx("small", { children: manifestLabel })] }), _jsxs("div", { children: [_jsx("span", { children: "Lanes" }), _jsx("b", { children: text(openLaneCount || lanes.length) }), _jsxs("small", { children: [text(activeLaneCount), " active \u00B7 ", text(lanes.length), " defined"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Open tasks" }), _jsx("b", { children: text(queuedTaskCount) }), _jsxs("small", { children: [text(numberValue(capacity.totalTaskCount)), " manifest tasks"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Source" }), _jsx("b", { children: text(numberValue(capacity.computeMaxConcurrency) || maxConcurrency || runningAgentCount) }), _jsx("small", { children: artifactLabel(textValue(capacity.manifestPath, 'manifest')) })] })] }) });
399
519
  }
400
520
  function AgentWorkerCard({ worker, index, now }) {
401
521
  const model = agentModelSummary(worker);
402
522
  const files = agentTouchedFiles(worker);
403
523
  const evidenceCount = agentEvidenceCount(worker);
524
+ const fileFallback = evidenceCount ? 'Evidence only' : worker.status === 'active' ? 'Files pending' : 'No source files';
404
525
  const visibleJobs = worker.currentJobs.slice(0, 2);
405
526
  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] })] });
527
+ 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: fileFallback }) : null] }), evidenceCount ? _jsxs("small", { children: [text(evidenceCount), " evidence ", evidenceCount === 1 ? 'artifact' : 'artifacts'] }) : null] })] });
407
528
  }
408
529
  function TaskBoardCard({ job }) {
409
530
  const id = taskCardId(job);
410
531
  const ticket = ticketId(job);
411
532
  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"] })] })] });
533
+ 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: [_jsx("span", { children: pathSummaryText(job) }), _jsx("span", { children: evidenceSummaryText(job) })] })] });
413
534
  }
414
535
  function TaskDetailDialog({ job, dashboard }) {
415
536
  const title = taskTitle(job);
@@ -426,7 +547,10 @@ function TaskFileDiffs({ job, details }) {
426
547
  if (!details && job.boardKind !== 'backlog')
427
548
  return _jsxs("section", { className: "task-dialog-section", children: [_jsx("h4", { children: "Files changed" }), _jsx("p", { className: "empty tight", children: "Loading file diffs..." })] });
428
549
  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 })] })) })] });
550
+ return _jsxs("section", { className: "task-dialog-section", children: [_jsx("h4", { children: "Files changed" }), _jsx("div", { className: "task-file-list", children: details.files.map((file) => {
551
+ const keys = taskFileDiffKeys(job, file);
552
+ return _jsxs("details", { className: "task-file-diff", "data-task-file-diff-key": keys[0] ?? '', "data-task-file-diff-keys": keys.join('\t'), open: isTaskFileDiffOpen(job, file) ? 'open' : undefined, 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 })] });
553
+ }) })] });
430
554
  if (changedPaths.length)
431
555
  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
556
  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.' })] });
@@ -530,7 +654,7 @@ function taskBoardItems(dashboard, jobs) {
530
654
  ];
531
655
  }
532
656
  function isTaskBoardVisibleJob(job) {
533
- return !isResolvedCoordinatorReviewJob(job);
657
+ return !isResolvedCoordinatorReviewJob(job) || isSuccessfulResolvedCoordinatorReviewJob(job);
534
658
  }
535
659
  function isResolvedCoordinatorReviewJob(job) {
536
660
  const bucket = normalized(job.bucket);
@@ -543,6 +667,16 @@ function isResolvedCoordinatorDecisionStatus(status) {
543
667
  const value = normalized(status);
544
668
  return Boolean(value) && !['open', 'pending', 'deferred', 'needs-review'].includes(value);
545
669
  }
670
+ function isSuccessfulResolvedCoordinatorReviewJob(job) {
671
+ if (!isResolvedCoordinatorReviewJob(job))
672
+ return false;
673
+ const status = normalized(coordinatorDecisionStatus(job));
674
+ const disposition = normalized(job.disposition);
675
+ const jobStatus = normalized(job.status);
676
+ return jobStatus === 'completed'
677
+ || ['accepted', 'accepted-applied', 'applied', 'committed', 'landed'].includes(status)
678
+ || ['accepted', 'accepted-applied', 'applied', 'committed', 'landed'].includes(disposition);
679
+ }
546
680
  function activeAgentTaskCount(dashboard, jobs) {
547
681
  return taskBoardItems(dashboard, jobs).filter(isActiveAgentJob).length;
548
682
  }
@@ -558,9 +692,12 @@ function backlogBoardItems(dashboard) {
558
692
  return entries.map((entry, index) => {
559
693
  const id = textValue(entry.id ?? entry.taskId ?? entry.title, `backlog-${index + 1}`);
560
694
  const status = normalized(entry.status ?? entry.state);
561
- const column = ['todo', 'ready', 'queued', 'pending'].includes(status) || Boolean(entry.ready)
562
- ? 'todo'
563
- : 'backlog';
695
+ const requestedColumn = textValue(entry.boardColumn, '');
696
+ const column = ['backlog', 'todo', 'active', 'review', 'ready', 'done', 'blocked'].includes(requestedColumn)
697
+ ? requestedColumn
698
+ : ['todo', 'ready', 'queued', 'pending'].includes(status) || Boolean(entry.ready)
699
+ ? 'todo'
700
+ : 'backlog';
564
701
  return {
565
702
  ...entry,
566
703
  boardKind: 'backlog',
@@ -575,14 +712,15 @@ function backlogBoardItems(dashboard) {
575
712
  };
576
713
  });
577
714
  }
578
- function taskBoardColumns(items) {
715
+ function taskBoardColumns(items, sourceKind = 'snapshot') {
716
+ const doneDetail = sourceKind === 'lifetime' ? 'lifetime completed output' : 'completed output';
579
717
  const columns = [
580
718
  { id: 'backlog', title: 'Backlog', detail: 'not scheduled yet', empty: 'No backlog items.', items: [] },
581
719
  { id: 'todo', title: 'To do', detail: 'ready to start', empty: 'No queued tasks.', items: [] },
582
720
  { id: 'active', title: 'Active', detail: 'running work', empty: 'No active tasks.', items: [] },
583
721
  { id: 'review', title: 'Coordinator review', detail: 'needs decision', empty: 'No tasks need coordinator review.', items: [] },
584
722
  { 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: [] },
723
+ { id: 'done', title: 'Done', detail: doneDetail, empty: 'No completed tasks.', items: [] },
586
724
  { id: 'blocked', title: 'Blocked', detail: 'waiting or impossible', empty: 'No genuinely blocked tasks.', items: [] }
587
725
  ];
588
726
  const byId = new Map(columns.map((column) => [column.id, column]));
@@ -686,6 +824,13 @@ function agentDisplayName(worker, index) {
686
824
  return worker.name;
687
825
  return `Agent ${String(index + 1).padStart(2, '0')}`;
688
826
  }
827
+ function shortAgentId(value) {
828
+ const clean = value.split(/[\\/]/g).pop() ?? value;
829
+ const parts = clean.split('-').filter(Boolean);
830
+ if (parts.length <= 3)
831
+ return clean;
832
+ return parts.slice(-3).join('-');
833
+ }
689
834
  function agentColor(key) {
690
835
  return AGENT_COLORS[stableNumber(key) % AGENT_COLORS.length] ?? AGENT_COLORS[0];
691
836
  }
@@ -712,6 +857,8 @@ function agentStatus(job) {
712
857
  const disposition = normalized(job.disposition ?? job.mergeReadiness);
713
858
  if (['running', 'active', 'working', 'in-progress', 'in progress'].includes(status))
714
859
  return 'active';
860
+ if (isSuccessfulResolvedCoordinatorReviewJob(job))
861
+ return 'done';
715
862
  if (status === 'idle' || bucket === 'idle')
716
863
  return 'idle';
717
864
  if (isBlockedJob(job))
@@ -856,6 +1003,8 @@ function taskBoardColumnId(job) {
856
1003
  return job.boardColumn;
857
1004
  if (job.boardKind === 'backlog')
858
1005
  return 'backlog';
1006
+ if (isSuccessfulResolvedCoordinatorReviewJob(job))
1007
+ return 'done';
859
1008
  if (isBlockedJob(job))
860
1009
  return 'blocked';
861
1010
  if (isCoordinatorReviewJob(job))
@@ -900,6 +1049,8 @@ function taskSortRank(job) {
900
1049
  return 3;
901
1050
  if (isReadyJob(job))
902
1051
  return 4;
1052
+ if (isSuccessfulResolvedCoordinatorReviewJob(job))
1053
+ return 5;
903
1054
  return 5;
904
1055
  }
905
1056
  function taskCardId(job) {
@@ -990,6 +1141,8 @@ function coordinatorDecisionLabel(status) {
990
1141
  function coordinatorFacingSignalLabel(value) {
991
1142
  const raw = textValue(value, 'tracked task');
992
1143
  const normalizedValue = normalized(raw);
1144
+ if (normalizedValue === 'review-resolved' || normalizedValue === 'resolved-review')
1145
+ return 'review resolved';
993
1146
  if (normalizedValue === 'needs-human-port' || normalizedValue === 'needs-coordinator-port')
994
1147
  return 'needs coordinator review';
995
1148
  if (normalizedValue === 'needs-human-review' || normalizedValue === 'needs-coordinator-review')
@@ -998,6 +1151,8 @@ function coordinatorFacingSignalLabel(value) {
998
1151
  return 'needs coordinator decision';
999
1152
  if (normalizedValue === 'failed-evidence')
1000
1153
  return 'failed evidence';
1154
+ if (normalizedValue === 'worker-failed')
1155
+ return 'failed worker';
1001
1156
  if (normalizedValue === 'ready-to-apply')
1002
1157
  return 'ready to apply';
1003
1158
  if (normalizedValue === 'patch-candidate')
@@ -1017,12 +1172,16 @@ function taskTokenSummary(job) {
1017
1172
  return '-';
1018
1173
  }
1019
1174
  function taskReasonItems(job) {
1020
- return uniqueStrings([
1175
+ const reasons = uniqueStrings([
1021
1176
  ...stringArray(job.collectReasonClasses),
1022
1177
  ...stringArray(job.reasons),
1023
1178
  ...stringArray(job.contextBudgetWarnings),
1024
1179
  ...stringArray(job.semanticReadinessReasons)
1025
- ]).slice(0, 8);
1180
+ ]);
1181
+ const terminalOrResolved = isResolvedCoordinatorReviewJob(job) || (isCompletedJob(job) && !isNeedsCoordinatorPortJob(job));
1182
+ return reasons
1183
+ .filter((reason) => !terminalOrResolved || !isOpenCoordinatorReviewSignal(reason))
1184
+ .slice(0, 8);
1026
1185
  }
1027
1186
  function taskFact(label, value) {
1028
1187
  return _jsxs("div", { className: "task-fact", children: [_jsx("span", { children: label }), _jsx("b", { children: value || '-' })] });
@@ -1443,6 +1602,9 @@ function priorityRank(priority) {
1443
1602
  function successLikeJobCount(jobs) {
1444
1603
  return jobs.filter((job) => isCompletedJob(job) && !isFailedJob(job) && !isStaleJob(job)).length;
1445
1604
  }
1605
+ function resolvedWorkJobCount(jobs) {
1606
+ return jobs.filter((job) => isResolvedCoordinatorReviewJob(job) || (isCompletedJob(job) && !isFailedJob(job) && !isStaleJob(job))).length;
1607
+ }
1446
1608
  function laneLoadTone(lane) {
1447
1609
  if (lane.failedCount || lane.blockedCount)
1448
1610
  return 'bad';
@@ -1503,6 +1665,37 @@ function openTaskCard(id) {
1503
1665
  function taskDialogScrollId(id) {
1504
1666
  return `task-dialog-${id}`;
1505
1667
  }
1668
+ function captureTaskFileDiffOpenStates() {
1669
+ root?.querySelectorAll('details[data-task-file-diff-key]').forEach((entry) => {
1670
+ setTaskFileDiffOpenState(entry);
1671
+ });
1672
+ }
1673
+ function taskFileDiffKeys(job, file) {
1674
+ const filePath = textValue(file.path, '');
1675
+ if (!filePath)
1676
+ return [];
1677
+ return uniqueStrings([
1678
+ taskCardId(job),
1679
+ textValue(job.originalJobId, ''),
1680
+ textValue(job.jobId, ''),
1681
+ textValue(job.taskId, ''),
1682
+ textValue(job.workerId, ''),
1683
+ textValue(job.agentId, ''),
1684
+ ticketId(job)
1685
+ ].filter(Boolean)).map((id) => `${id}::${filePath}`);
1686
+ }
1687
+ function isTaskFileDiffOpen(job, file) {
1688
+ return taskFileDiffKeys(job, file).some((key) => taskFileDiffOpenStates.get(key));
1689
+ }
1690
+ function taskFileDiffElementKeys(entry) {
1691
+ const keys = textValue(entry.dataset.taskFileDiffKeys, '').split('\t').filter(Boolean);
1692
+ const primary = textValue(entry.dataset.taskFileDiffKey, '');
1693
+ return uniqueStrings([...keys, primary].filter(Boolean));
1694
+ }
1695
+ function setTaskFileDiffOpenState(entry) {
1696
+ for (const key of taskFileDiffElementKeys(entry))
1697
+ taskFileDiffOpenStates.set(key, entry.open);
1698
+ }
1506
1699
  async function fetchTaskDetails(job) {
1507
1700
  const id = taskCardId(job);
1508
1701
  if (!id || taskDetailsCache.has(id) || taskDetailsPending.has(id))
@@ -1513,7 +1706,11 @@ async function fetchTaskDetails(job) {
1513
1706
  }
1514
1707
  taskDetailsPending.add(id);
1515
1708
  try {
1516
- const response = await fetch(`/api/task-details?id=${encodeURIComponent(id)}`);
1709
+ const params = new URLSearchParams({ id });
1710
+ const sourceRun = textValue(job.sourceRun, '');
1711
+ if (sourceRun)
1712
+ params.set('sourceRun', sourceRun);
1713
+ const response = await fetch(`/api/task-details?${params}`);
1517
1714
  const details = await response.json();
1518
1715
  taskDetailsCache.set(id, details);
1519
1716
  }
@@ -2052,6 +2249,16 @@ function dashboardSourceSmokeMarker(kind) {
2052
2249
  return 'stopped-source-strip';
2053
2250
  return 'snapshot-source-strip';
2054
2251
  }
2252
+ function boardScopeLabel(dashboard) {
2253
+ const kind = dashboardSourceKind(dashboard);
2254
+ if (kind === 'lifetime')
2255
+ return 'lifetime';
2256
+ if (kind === 'live' || kind === 'stopped')
2257
+ return 'run';
2258
+ if (kind === 'demo')
2259
+ return 'demo';
2260
+ return 'snapshot';
2261
+ }
2055
2262
  function riskStatusLabel(dashboard, attention, loaded) {
2056
2263
  if (attention.failedCount || attention.staleCount || attention.needsCoordinatorReviewCount || attention.blockedCount)
2057
2264
  return 'Attention needed';
@@ -2060,6 +2267,8 @@ function riskStatusLabel(dashboard, attention, loaded) {
2060
2267
  return dashboard.ok ? 'Admission clean' : 'Run degraded';
2061
2268
  }
2062
2269
  function jobRisk(job) {
2270
+ if (isSuccessfulResolvedCoordinatorReviewJob(job))
2271
+ return 'done';
2063
2272
  if (isBlockedJob(job))
2064
2273
  return 'failed';
2065
2274
  if (isNeedsCoordinatorPortJob(job))
@@ -2080,6 +2289,8 @@ function isCompletedJob(job) {
2080
2289
  return normalized(job.status) === 'completed';
2081
2290
  }
2082
2291
  function isReadyJob(job) {
2292
+ if (isResolvedCoordinatorReviewJob(job))
2293
+ return false;
2083
2294
  return normalized(job.bucket) === 'ready-to-apply'
2084
2295
  || normalized(job.disposition) === 'ready-to-apply'
2085
2296
  || normalized(job.mergeReadiness).includes('ready');
@@ -2089,7 +2300,7 @@ function jobRiskLabel(job) {
2089
2300
  if (isBlockedJob(job))
2090
2301
  return 'blocked';
2091
2302
  if (risk === 'failed')
2092
- return 'review';
2303
+ return 'failed';
2093
2304
  if (risk === 'stale')
2094
2305
  return 'stale';
2095
2306
  if (risk === 'review')
@@ -2101,17 +2312,22 @@ function jobRiskLabel(job) {
2101
2312
  return text(job.status);
2102
2313
  }
2103
2314
  function isFailedJob(job) {
2315
+ if (isResolvedCoordinatorReviewJob(job))
2316
+ return false;
2104
2317
  const status = normalized(job.status);
2105
2318
  const bucket = normalized(job.bucket);
2106
2319
  const disposition = normalized(job.disposition);
2107
2320
  const readiness = normalized(job.mergeReadiness);
2108
2321
  return status === 'failed'
2109
2322
  || bucket === 'failed-evidence'
2323
+ || bucket === 'worker-failed'
2110
2324
  || disposition === 'rejected'
2111
2325
  || disposition === 'failed'
2112
2326
  || readiness.includes('failed');
2113
2327
  }
2114
2328
  function isBlockedJob(job) {
2329
+ if (isResolvedCoordinatorReviewJob(job))
2330
+ return false;
2115
2331
  const status = normalized(job.status);
2116
2332
  const bucket = normalized(job.bucket);
2117
2333
  const disposition = normalized(job.disposition);
@@ -2127,9 +2343,13 @@ function isNeedsCoordinatorReviewJob(job) {
2127
2343
  return isNeedsCoordinatorPortJob(job);
2128
2344
  }
2129
2345
  function isNeedsCoordinatorPortJob(job) {
2346
+ if (isResolvedCoordinatorReviewJob(job))
2347
+ return false;
2130
2348
  const bucket = normalized(job.bucket);
2131
2349
  const disposition = normalized(job.disposition);
2132
2350
  const readiness = normalized(job.mergeReadiness);
2351
+ if (isCompletedJob(job) && ['completed', 'done', 'landed', 'applied'].includes(bucket))
2352
+ return false;
2133
2353
  return bucket === 'needs-human-port'
2134
2354
  || bucket === 'needs-human-review'
2135
2355
  || bucket === 'needs-human-decision'
@@ -2140,16 +2360,29 @@ function isNeedsCoordinatorPortJob(job) {
2140
2360
  || readiness.includes('needs')
2141
2361
  || normalized(job.status) === 'needs-review';
2142
2362
  }
2363
+ function isOpenCoordinatorReviewSignal(value) {
2364
+ const signal = normalized(value);
2365
+ return signal === 'needs-human-port'
2366
+ || signal === 'needs-human-review'
2367
+ || signal === 'needs-human-decision'
2368
+ || signal === 'needs-coordinator-port'
2369
+ || signal === 'needs-coordinator-review'
2370
+ || signal === 'needs-coordinator-decision'
2371
+ || signal === 'needs-port'
2372
+ || signal === 'needs-review'
2373
+ || signal === 'manual-port-required'
2374
+ || signal === 'manual port required';
2375
+ }
2143
2376
  function isCoordinatorReviewJob(job) {
2144
2377
  if (isResolvedCoordinatorReviewJob(job))
2145
2378
  return false;
2146
2379
  return isNeedsCoordinatorPortJob(job)
2147
- || isFailedJob(job)
2148
- || isStaleJob(job)
2149
2380
  || normalized(job.disposition).includes('review')
2150
2381
  || normalized(job.mergeReadiness).includes('review');
2151
2382
  }
2152
2383
  function isStaleJob(job) {
2384
+ if (isResolvedCoordinatorReviewJob(job))
2385
+ return false;
2153
2386
  return normalized(job.bucket).includes('stale')
2154
2387
  || normalized(job.disposition).includes('stale')
2155
2388
  || normalized(job.mergeReadiness).includes('stale');
@@ -2197,6 +2430,8 @@ function pathSummaryText(job) {
2197
2430
  const ignoredOwnership = ignoredOwnershipViolations(job);
2198
2431
  const ignoredChangedPathCount = numberValue(job.ignoredChangedPathCount);
2199
2432
  const quarantinedChangedPathCount = numberValue(job.quarantinedChangedPathCount);
2433
+ const changedPathCount = numberValue(job.changedPathCount);
2434
+ const evidencePathCount = numberValue(job.evidencePathCount);
2200
2435
  const ignoredCount = ignoredOwnership.length + ignoredChangedPathCount;
2201
2436
  if (sourceOwnership.length)
2202
2437
  return `${text(sourceOwnership.length)} source issue`;
@@ -2204,7 +2439,27 @@ function pathSummaryText(job) {
2204
2439
  return `${text(quarantinedChangedPathCount)} quarantined`;
2205
2440
  if (ignoredCount)
2206
2441
  return `${text(ignoredCount)} generated noise`;
2207
- return `${text(job.changedPathCount)} changed paths`;
2442
+ if (!changedPathCount && hasPatchArtifact(job))
2443
+ return 'patch not indexed yet';
2444
+ if (!changedPathCount && evidencePathCount)
2445
+ return 'evidence only';
2446
+ if (!changedPathCount && isActiveAgentJob(job))
2447
+ return 'files pending';
2448
+ return `${text(changedPathCount)} changed ${changedPathCount === 1 ? 'path' : 'paths'}`;
2449
+ }
2450
+ function hasPatchArtifact(job) {
2451
+ const paths = [
2452
+ textValue(job.patchPath, ''),
2453
+ ...stringArray(job.artifactPaths),
2454
+ ...stringArray(job.evidencePaths)
2455
+ ];
2456
+ return paths.some((entry) => /\.patch(?:$|[?#])/.test(entry));
2457
+ }
2458
+ function evidenceSummaryText(job) {
2459
+ const count = numberValue(job.evidencePathCount);
2460
+ if (!count)
2461
+ return 'no evidence yet';
2462
+ return `${formatNumber(count)} evidence ${count === 1 ? 'artifact' : 'artifacts'}`;
2208
2463
  }
2209
2464
  function sourceOwnershipViolations(job) {
2210
2465
  const explicit = stringArray(job.sourceOwnershipViolations);
@@ -2498,6 +2753,8 @@ function tabMeta(tab, input) {
2498
2753
  return 'AI tasks';
2499
2754
  if (tab === 'swarm')
2500
2755
  return 'active agents';
2756
+ if (tab === 'lanes')
2757
+ return `${text(input.lanes)} lanes`;
2501
2758
  if (tab === 'performance')
2502
2759
  return 'cost trends';
2503
2760
  if (tab === 'history')
@@ -3385,16 +3642,17 @@ const MODEL_PRICES_PER_MILLION = [
3385
3642
  ];
3386
3643
  function modelCostSummary(jobs) {
3387
3644
  const allCosts = jobs.map(modelCostForJob);
3388
- const costs = allCosts.filter((entry) => entry.price);
3645
+ const costs = allCosts.filter((entry) => entry.costKnown);
3389
3646
  const unpricedCount = allCosts.length - costs.length;
3390
3647
  const total = costs.reduce((sum, entry) => sum + entry.cost, 0);
3391
3648
  if (!costs.length)
3392
3649
  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;
3650
+ const billableTokenJobs = costs.filter((entry) => entry.cost > 0 || entry.uncachedInputTokens > 0 || entry.cachedInputTokens > 0 || entry.outputTokens > 0).length;
3394
3651
  const models = uniqueStrings(costs.map((entry) => entry.model));
3395
3652
  const outputJobs = costs.filter((entry) => entry.outputTokens > 0).length;
3396
3653
  const estimated = costs.filter((entry) => entry.estimatedInput).length;
3397
3654
  const longContextJobs = costs.filter((entry) => entry.longContext).length;
3655
+ const backendEstimated = costs.filter((entry) => entry.backendEstimate).length;
3398
3656
  const pricingSources = uniqueStrings(costs.map((entry) => entry.pricingSource).filter((value) => Boolean(value)));
3399
3657
  const modelText = models.length > 3 ? `${models.slice(0, 3).join(', ')} +${models.length - 3}` : models.join(', ');
3400
3658
  const sourceText = pricingSources.length ? ` · ${pricingSources.join(', ')}` : '';
@@ -3405,20 +3663,21 @@ function modelCostSummary(jobs) {
3405
3663
  const scope = outputJobs === 0 ? 'input-only' : outputJobs === costs.length ? 'input+output' : 'input+partial output';
3406
3664
  const estimatedText = estimated ? ` · ${formatNumber(estimated)} estimated-input jobs` : '';
3407
3665
  const longContextText = longContextJobs ? ` · ${formatNumber(longContextJobs)} long-context priced` : '';
3666
+ const backendText = backendEstimated ? ` · ${formatNumber(backendEstimated)} backend-priced jobs` : '';
3408
3667
  const missingOutputText = outputJobs > 0 && outputJobs < costs.length ? ` · ${formatNumber(costs.length - outputJobs)} without output tokens` : '';
3409
3668
  const noOutputText = outputJobs === 0 ? ' · no output tokens reported' : '';
3410
- return { value: formatUsd(total), detail: `${scope} estimate · ${modelText}${sourceText}${longContextText}${estimatedText}${missingOutputText}${noOutputText}${unpricedText}` };
3669
+ return { value: formatUsd(total), detail: `${scope} estimate · ${modelText}${sourceText}${longContextText}${backendText}${estimatedText}${missingOutputText}${noOutputText}${unpricedText}` };
3411
3670
  }
3412
3671
  function modelCostRows(jobs) {
3413
3672
  return jobs
3414
3673
  .map((job) => ({ job, cost: modelCostForJob(job) }))
3415
- .filter((entry) => entry.cost.price && entry.cost.cost > 0)
3674
+ .filter((entry) => entry.cost.costKnown && entry.cost.cost > 0)
3416
3675
  .sort((left, right) => right.cost.cost - left.cost.cost)
3417
3676
  .slice(0, 5)
3418
3677
  .map(({ job, cost }) => ({
3419
3678
  label: `${ticketId(job)} · ${taskTitle(job)}`,
3420
3679
  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' : ''}`
3680
+ detail: `${cost.model} estimate · ${formatNumber(cost.uncachedInputTokens)} uncached · ${formatNumber(cost.cachedInputTokens)} cached${cost.outputTokens ? ` · ${formatNumber(cost.outputTokens)} output` : ''}${cost.longContext ? ' · long context' : ''}${cost.backendEstimate ? ' · backend priced' : ''}${cost.estimatedInput ? ' · estimated input' : ''}`
3422
3681
  }));
3423
3682
  }
3424
3683
  function modelCostForJob(job) {
@@ -3426,12 +3685,32 @@ function modelCostForJob(job) {
3426
3685
  const priceEntry = modelPriceEntryForModel(model);
3427
3686
  const input = inputTokenBreakdownForJob(job);
3428
3687
  const outputTokens = jobOutputTokens(job);
3688
+ const backendCost = backendCostEstimateForJob(job);
3429
3689
  const longContext = Boolean(priceEntry?.longContextPrice && priceEntry.longContextThreshold && input.inputTokens > priceEntry.longContextThreshold);
3430
3690
  const price = priceEntry ? (longContext ? priceEntry.longContextPrice ?? priceEntry.price : priceEntry.price) : undefined;
3691
+ if (backendCost > 0) {
3692
+ return {
3693
+ model: textValue(job.pricingModel ?? job.pricingMatchedModel, model),
3694
+ price,
3695
+ pricingSource: textValue(job.pricingSource, 'backend cost estimate'),
3696
+ ...input,
3697
+ outputTokens,
3698
+ longContext,
3699
+ backendEstimate: true,
3700
+ costKnown: true,
3701
+ cost: backendCost
3702
+ };
3703
+ }
3431
3704
  if (!price)
3432
- return { model, ...input, outputTokens, longContext: false, cost: 0 };
3705
+ return { model, ...input, outputTokens, longContext: false, backendEstimate: false, costKnown: false, cost: 0 };
3433
3706
  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 };
3707
+ return { model, price, pricingSource: priceEntry?.source, ...input, outputTokens, longContext, backendEstimate: false, costKnown: true, cost };
3708
+ }
3709
+ function backendCostEstimateForJob(job) {
3710
+ const microUsd = firstPositiveNumber(job.estimatedCostMicroUsd, job.estimated_cost_micro_usd);
3711
+ if (microUsd > 0)
3712
+ return microUsd / 1_000_000;
3713
+ return firstPositiveNumber(job.estimatedCostUsd, job.estimated_cost_usd);
3435
3714
  }
3436
3715
  function modelPriceEntryForModel(model) {
3437
3716
  const key = normalizedModelKey(model);
@@ -3459,7 +3738,7 @@ function inputTokenBreakdownForJob(job) {
3459
3738
  const inputDetails = recordValue(job.inputTokensDetails ?? job.input_tokens_details ?? usage.input_tokens_details);
3460
3739
  const promptDetails = recordValue(job.promptTokensDetails ?? job.prompt_tokens_details ?? usage.prompt_tokens_details);
3461
3740
  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);
3741
+ const estimatedInput = firstPositiveNumber(job.estimatedInputTokens, job.estimated_input_tokens, usage.estimated_input_tokens, usage.estimatedInputTokens);
3463
3742
  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
3743
  const reportedUncached = firstPositiveNumber(job.uncachedInputTokens, job.uncached_input_tokens, usage.uncached_input_tokens);
3465
3744
  const inputTokens = actualInput || estimatedInput || reportedCached + reportedUncached;