@shapeshift-labs/frontier-loom-ui 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  }
@@ -394,33 +416,121 @@ function costMetric(label, value, detail) {
394
416
  }
395
417
  function TaskBoard({ dashboard, jobs }) {
396
418
  const items = taskBoardItems(dashboard, jobs);
397
- const columns = taskBoardColumns(items);
419
+ const sourceKind = dashboardSourceKind(dashboard);
420
+ const scope = boardScopeLabel(dashboard);
421
+ const columns = taskBoardColumns(items, sourceKind);
398
422
  const activeCount = columns.reduce((sum, column) => column.id === 'done' ? sum : sum + column.items.length, 0);
399
- 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
400
424
  ? column.items.map((job) => _jsx(TaskBoardCard, { job: job }))
401
425
  : _jsx("p", { className: "empty tight", children: column.empty }) })] })) }) });
402
426
  }
403
427
  function AgentWork({ dashboard, jobs }) {
404
428
  const items = taskBoardItems(dashboard, jobs).filter(isActiveAgentJob);
405
429
  const workers = agentWorkers(items);
406
- void dashboard;
407
- 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
408
- ? workers.map((worker, index) => _jsx(AgentWorkerCard, { worker: worker, index: index, now: timeValue(dashboard.generatedAt) ?? Date.now() }))
409
- : _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')) })] })] }) });
410
519
  }
411
520
  function AgentWorkerCard({ worker, index, now }) {
412
521
  const model = agentModelSummary(worker);
413
522
  const files = agentTouchedFiles(worker);
414
523
  const evidenceCount = agentEvidenceCount(worker);
524
+ const fileFallback = evidenceCount ? 'Evidence only' : worker.status === 'active' ? 'Files pending' : 'No source files';
415
525
  const visibleJobs = worker.currentJobs.slice(0, 2);
416
526
  const hiddenJobCount = Math.max(0, worker.currentJobs.length - visibleJobs.length);
417
- 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] })] });
418
528
  }
419
529
  function TaskBoardCard({ job }) {
420
530
  const id = taskCardId(job);
421
531
  const ticket = ticketId(job);
422
532
  const risk = jobRisk(job);
423
- 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) })] })] });
424
534
  }
425
535
  function TaskDetailDialog({ job, dashboard }) {
426
536
  const title = taskTitle(job);
@@ -437,7 +547,10 @@ function TaskFileDiffs({ job, details }) {
437
547
  if (!details && job.boardKind !== 'backlog')
438
548
  return _jsxs("section", { className: "task-dialog-section", children: [_jsx("h4", { children: "Files changed" }), _jsx("p", { className: "empty tight", children: "Loading file diffs..." })] });
439
549
  if (details?.files?.length)
440
- 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
+ }) })] });
441
554
  if (changedPaths.length)
442
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 })] });
443
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.' })] });
@@ -541,7 +654,7 @@ function taskBoardItems(dashboard, jobs) {
541
654
  ];
542
655
  }
543
656
  function isTaskBoardVisibleJob(job) {
544
- return !isResolvedCoordinatorReviewJob(job);
657
+ return !isResolvedCoordinatorReviewJob(job) || isSuccessfulResolvedCoordinatorReviewJob(job);
545
658
  }
546
659
  function isResolvedCoordinatorReviewJob(job) {
547
660
  const bucket = normalized(job.bucket);
@@ -554,6 +667,16 @@ function isResolvedCoordinatorDecisionStatus(status) {
554
667
  const value = normalized(status);
555
668
  return Boolean(value) && !['open', 'pending', 'deferred', 'needs-review'].includes(value);
556
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
+ }
557
680
  function activeAgentTaskCount(dashboard, jobs) {
558
681
  return taskBoardItems(dashboard, jobs).filter(isActiveAgentJob).length;
559
682
  }
@@ -569,9 +692,12 @@ function backlogBoardItems(dashboard) {
569
692
  return entries.map((entry, index) => {
570
693
  const id = textValue(entry.id ?? entry.taskId ?? entry.title, `backlog-${index + 1}`);
571
694
  const status = normalized(entry.status ?? entry.state);
572
- const column = ['todo', 'ready', 'queued', 'pending'].includes(status) || Boolean(entry.ready)
573
- ? 'todo'
574
- : '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';
575
701
  return {
576
702
  ...entry,
577
703
  boardKind: 'backlog',
@@ -586,14 +712,15 @@ function backlogBoardItems(dashboard) {
586
712
  };
587
713
  });
588
714
  }
589
- function taskBoardColumns(items) {
715
+ function taskBoardColumns(items, sourceKind = 'snapshot') {
716
+ const doneDetail = sourceKind === 'lifetime' ? 'lifetime completed output' : 'completed output';
590
717
  const columns = [
591
718
  { id: 'backlog', title: 'Backlog', detail: 'not scheduled yet', empty: 'No backlog items.', items: [] },
592
719
  { id: 'todo', title: 'To do', detail: 'ready to start', empty: 'No queued tasks.', items: [] },
593
720
  { id: 'active', title: 'Active', detail: 'running work', empty: 'No active tasks.', items: [] },
594
721
  { id: 'review', title: 'Coordinator review', detail: 'needs decision', empty: 'No tasks need coordinator review.', items: [] },
595
722
  { id: 'ready', title: 'Ready', detail: 'ready to apply', empty: 'No ready patches.', items: [] },
596
- { id: 'done', title: 'Done', detail: 'completed output', empty: 'No completed tasks.', items: [] },
723
+ { id: 'done', title: 'Done', detail: doneDetail, empty: 'No completed tasks.', items: [] },
597
724
  { id: 'blocked', title: 'Blocked', detail: 'waiting or impossible', empty: 'No genuinely blocked tasks.', items: [] }
598
725
  ];
599
726
  const byId = new Map(columns.map((column) => [column.id, column]));
@@ -697,6 +824,13 @@ function agentDisplayName(worker, index) {
697
824
  return worker.name;
698
825
  return `Agent ${String(index + 1).padStart(2, '0')}`;
699
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
+ }
700
834
  function agentColor(key) {
701
835
  return AGENT_COLORS[stableNumber(key) % AGENT_COLORS.length] ?? AGENT_COLORS[0];
702
836
  }
@@ -723,6 +857,8 @@ function agentStatus(job) {
723
857
  const disposition = normalized(job.disposition ?? job.mergeReadiness);
724
858
  if (['running', 'active', 'working', 'in-progress', 'in progress'].includes(status))
725
859
  return 'active';
860
+ if (isSuccessfulResolvedCoordinatorReviewJob(job))
861
+ return 'done';
726
862
  if (status === 'idle' || bucket === 'idle')
727
863
  return 'idle';
728
864
  if (isBlockedJob(job))
@@ -867,6 +1003,8 @@ function taskBoardColumnId(job) {
867
1003
  return job.boardColumn;
868
1004
  if (job.boardKind === 'backlog')
869
1005
  return 'backlog';
1006
+ if (isSuccessfulResolvedCoordinatorReviewJob(job))
1007
+ return 'done';
870
1008
  if (isBlockedJob(job))
871
1009
  return 'blocked';
872
1010
  if (isCoordinatorReviewJob(job))
@@ -911,6 +1049,8 @@ function taskSortRank(job) {
911
1049
  return 3;
912
1050
  if (isReadyJob(job))
913
1051
  return 4;
1052
+ if (isSuccessfulResolvedCoordinatorReviewJob(job))
1053
+ return 5;
914
1054
  return 5;
915
1055
  }
916
1056
  function taskCardId(job) {
@@ -1001,6 +1141,8 @@ function coordinatorDecisionLabel(status) {
1001
1141
  function coordinatorFacingSignalLabel(value) {
1002
1142
  const raw = textValue(value, 'tracked task');
1003
1143
  const normalizedValue = normalized(raw);
1144
+ if (normalizedValue === 'review-resolved' || normalizedValue === 'resolved-review')
1145
+ return 'review resolved';
1004
1146
  if (normalizedValue === 'needs-human-port' || normalizedValue === 'needs-coordinator-port')
1005
1147
  return 'needs coordinator review';
1006
1148
  if (normalizedValue === 'needs-human-review' || normalizedValue === 'needs-coordinator-review')
@@ -1009,6 +1151,8 @@ function coordinatorFacingSignalLabel(value) {
1009
1151
  return 'needs coordinator decision';
1010
1152
  if (normalizedValue === 'failed-evidence')
1011
1153
  return 'failed evidence';
1154
+ if (normalizedValue === 'worker-failed')
1155
+ return 'failed worker';
1012
1156
  if (normalizedValue === 'ready-to-apply')
1013
1157
  return 'ready to apply';
1014
1158
  if (normalizedValue === 'patch-candidate')
@@ -1028,12 +1172,16 @@ function taskTokenSummary(job) {
1028
1172
  return '-';
1029
1173
  }
1030
1174
  function taskReasonItems(job) {
1031
- return uniqueStrings([
1175
+ const reasons = uniqueStrings([
1032
1176
  ...stringArray(job.collectReasonClasses),
1033
1177
  ...stringArray(job.reasons),
1034
1178
  ...stringArray(job.contextBudgetWarnings),
1035
1179
  ...stringArray(job.semanticReadinessReasons)
1036
- ]).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);
1037
1185
  }
1038
1186
  function taskFact(label, value) {
1039
1187
  return _jsxs("div", { className: "task-fact", children: [_jsx("span", { children: label }), _jsx("b", { children: value || '-' })] });
@@ -1517,6 +1665,37 @@ function openTaskCard(id) {
1517
1665
  function taskDialogScrollId(id) {
1518
1666
  return `task-dialog-${id}`;
1519
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
+ }
1520
1699
  async function fetchTaskDetails(job) {
1521
1700
  const id = taskCardId(job);
1522
1701
  if (!id || taskDetailsCache.has(id) || taskDetailsPending.has(id))
@@ -1527,7 +1706,11 @@ async function fetchTaskDetails(job) {
1527
1706
  }
1528
1707
  taskDetailsPending.add(id);
1529
1708
  try {
1530
- 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}`);
1531
1714
  const details = await response.json();
1532
1715
  taskDetailsCache.set(id, details);
1533
1716
  }
@@ -2066,6 +2249,16 @@ function dashboardSourceSmokeMarker(kind) {
2066
2249
  return 'stopped-source-strip';
2067
2250
  return 'snapshot-source-strip';
2068
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
+ }
2069
2262
  function riskStatusLabel(dashboard, attention, loaded) {
2070
2263
  if (attention.failedCount || attention.staleCount || attention.needsCoordinatorReviewCount || attention.blockedCount)
2071
2264
  return 'Attention needed';
@@ -2074,6 +2267,8 @@ function riskStatusLabel(dashboard, attention, loaded) {
2074
2267
  return dashboard.ok ? 'Admission clean' : 'Run degraded';
2075
2268
  }
2076
2269
  function jobRisk(job) {
2270
+ if (isSuccessfulResolvedCoordinatorReviewJob(job))
2271
+ return 'done';
2077
2272
  if (isBlockedJob(job))
2078
2273
  return 'failed';
2079
2274
  if (isNeedsCoordinatorPortJob(job))
@@ -2094,6 +2289,8 @@ function isCompletedJob(job) {
2094
2289
  return normalized(job.status) === 'completed';
2095
2290
  }
2096
2291
  function isReadyJob(job) {
2292
+ if (isResolvedCoordinatorReviewJob(job))
2293
+ return false;
2097
2294
  return normalized(job.bucket) === 'ready-to-apply'
2098
2295
  || normalized(job.disposition) === 'ready-to-apply'
2099
2296
  || normalized(job.mergeReadiness).includes('ready');
@@ -2103,7 +2300,7 @@ function jobRiskLabel(job) {
2103
2300
  if (isBlockedJob(job))
2104
2301
  return 'blocked';
2105
2302
  if (risk === 'failed')
2106
- return 'review';
2303
+ return 'failed';
2107
2304
  if (risk === 'stale')
2108
2305
  return 'stale';
2109
2306
  if (risk === 'review')
@@ -2115,17 +2312,22 @@ function jobRiskLabel(job) {
2115
2312
  return text(job.status);
2116
2313
  }
2117
2314
  function isFailedJob(job) {
2315
+ if (isResolvedCoordinatorReviewJob(job))
2316
+ return false;
2118
2317
  const status = normalized(job.status);
2119
2318
  const bucket = normalized(job.bucket);
2120
2319
  const disposition = normalized(job.disposition);
2121
2320
  const readiness = normalized(job.mergeReadiness);
2122
2321
  return status === 'failed'
2123
2322
  || bucket === 'failed-evidence'
2323
+ || bucket === 'worker-failed'
2124
2324
  || disposition === 'rejected'
2125
2325
  || disposition === 'failed'
2126
2326
  || readiness.includes('failed');
2127
2327
  }
2128
2328
  function isBlockedJob(job) {
2329
+ if (isResolvedCoordinatorReviewJob(job))
2330
+ return false;
2129
2331
  const status = normalized(job.status);
2130
2332
  const bucket = normalized(job.bucket);
2131
2333
  const disposition = normalized(job.disposition);
@@ -2141,9 +2343,13 @@ function isNeedsCoordinatorReviewJob(job) {
2141
2343
  return isNeedsCoordinatorPortJob(job);
2142
2344
  }
2143
2345
  function isNeedsCoordinatorPortJob(job) {
2346
+ if (isResolvedCoordinatorReviewJob(job))
2347
+ return false;
2144
2348
  const bucket = normalized(job.bucket);
2145
2349
  const disposition = normalized(job.disposition);
2146
2350
  const readiness = normalized(job.mergeReadiness);
2351
+ if (isCompletedJob(job) && ['completed', 'done', 'landed', 'applied'].includes(bucket))
2352
+ return false;
2147
2353
  return bucket === 'needs-human-port'
2148
2354
  || bucket === 'needs-human-review'
2149
2355
  || bucket === 'needs-human-decision'
@@ -2154,16 +2360,29 @@ function isNeedsCoordinatorPortJob(job) {
2154
2360
  || readiness.includes('needs')
2155
2361
  || normalized(job.status) === 'needs-review';
2156
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
+ }
2157
2376
  function isCoordinatorReviewJob(job) {
2158
2377
  if (isResolvedCoordinatorReviewJob(job))
2159
2378
  return false;
2160
2379
  return isNeedsCoordinatorPortJob(job)
2161
- || isFailedJob(job)
2162
- || isStaleJob(job)
2163
2380
  || normalized(job.disposition).includes('review')
2164
2381
  || normalized(job.mergeReadiness).includes('review');
2165
2382
  }
2166
2383
  function isStaleJob(job) {
2384
+ if (isResolvedCoordinatorReviewJob(job))
2385
+ return false;
2167
2386
  return normalized(job.bucket).includes('stale')
2168
2387
  || normalized(job.disposition).includes('stale')
2169
2388
  || normalized(job.mergeReadiness).includes('stale');
@@ -2211,6 +2430,8 @@ function pathSummaryText(job) {
2211
2430
  const ignoredOwnership = ignoredOwnershipViolations(job);
2212
2431
  const ignoredChangedPathCount = numberValue(job.ignoredChangedPathCount);
2213
2432
  const quarantinedChangedPathCount = numberValue(job.quarantinedChangedPathCount);
2433
+ const changedPathCount = numberValue(job.changedPathCount);
2434
+ const evidencePathCount = numberValue(job.evidencePathCount);
2214
2435
  const ignoredCount = ignoredOwnership.length + ignoredChangedPathCount;
2215
2436
  if (sourceOwnership.length)
2216
2437
  return `${text(sourceOwnership.length)} source issue`;
@@ -2218,7 +2439,27 @@ function pathSummaryText(job) {
2218
2439
  return `${text(quarantinedChangedPathCount)} quarantined`;
2219
2440
  if (ignoredCount)
2220
2441
  return `${text(ignoredCount)} generated noise`;
2221
- 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'}`;
2222
2463
  }
2223
2464
  function sourceOwnershipViolations(job) {
2224
2465
  const explicit = stringArray(job.sourceOwnershipViolations);
@@ -2512,6 +2753,8 @@ function tabMeta(tab, input) {
2512
2753
  return 'AI tasks';
2513
2754
  if (tab === 'swarm')
2514
2755
  return 'active agents';
2756
+ if (tab === 'lanes')
2757
+ return `${text(input.lanes)} lanes`;
2515
2758
  if (tab === 'performance')
2516
2759
  return 'cost trends';
2517
2760
  if (tab === 'history')
@@ -3399,16 +3642,17 @@ const MODEL_PRICES_PER_MILLION = [
3399
3642
  ];
3400
3643
  function modelCostSummary(jobs) {
3401
3644
  const allCosts = jobs.map(modelCostForJob);
3402
- const costs = allCosts.filter((entry) => entry.price);
3645
+ const costs = allCosts.filter((entry) => entry.costKnown);
3403
3646
  const unpricedCount = allCosts.length - costs.length;
3404
3647
  const total = costs.reduce((sum, entry) => sum + entry.cost, 0);
3405
3648
  if (!costs.length)
3406
3649
  return { value: '-', detail: 'no priced model labels in this run' };
3407
- 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;
3408
3651
  const models = uniqueStrings(costs.map((entry) => entry.model));
3409
3652
  const outputJobs = costs.filter((entry) => entry.outputTokens > 0).length;
3410
3653
  const estimated = costs.filter((entry) => entry.estimatedInput).length;
3411
3654
  const longContextJobs = costs.filter((entry) => entry.longContext).length;
3655
+ const backendEstimated = costs.filter((entry) => entry.backendEstimate).length;
3412
3656
  const pricingSources = uniqueStrings(costs.map((entry) => entry.pricingSource).filter((value) => Boolean(value)));
3413
3657
  const modelText = models.length > 3 ? `${models.slice(0, 3).join(', ')} +${models.length - 3}` : models.join(', ');
3414
3658
  const sourceText = pricingSources.length ? ` · ${pricingSources.join(', ')}` : '';
@@ -3419,20 +3663,21 @@ function modelCostSummary(jobs) {
3419
3663
  const scope = outputJobs === 0 ? 'input-only' : outputJobs === costs.length ? 'input+output' : 'input+partial output';
3420
3664
  const estimatedText = estimated ? ` · ${formatNumber(estimated)} estimated-input jobs` : '';
3421
3665
  const longContextText = longContextJobs ? ` · ${formatNumber(longContextJobs)} long-context priced` : '';
3666
+ const backendText = backendEstimated ? ` · ${formatNumber(backendEstimated)} backend-priced jobs` : '';
3422
3667
  const missingOutputText = outputJobs > 0 && outputJobs < costs.length ? ` · ${formatNumber(costs.length - outputJobs)} without output tokens` : '';
3423
3668
  const noOutputText = outputJobs === 0 ? ' · no output tokens reported' : '';
3424
- 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}` };
3425
3670
  }
3426
3671
  function modelCostRows(jobs) {
3427
3672
  return jobs
3428
3673
  .map((job) => ({ job, cost: modelCostForJob(job) }))
3429
- .filter((entry) => entry.cost.price && entry.cost.cost > 0)
3674
+ .filter((entry) => entry.cost.costKnown && entry.cost.cost > 0)
3430
3675
  .sort((left, right) => right.cost.cost - left.cost.cost)
3431
3676
  .slice(0, 5)
3432
3677
  .map(({ job, cost }) => ({
3433
3678
  label: `${ticketId(job)} · ${taskTitle(job)}`,
3434
3679
  value: formatUsd(cost.cost),
3435
- 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' : ''}`
3436
3681
  }));
3437
3682
  }
3438
3683
  function modelCostForJob(job) {
@@ -3440,12 +3685,32 @@ function modelCostForJob(job) {
3440
3685
  const priceEntry = modelPriceEntryForModel(model);
3441
3686
  const input = inputTokenBreakdownForJob(job);
3442
3687
  const outputTokens = jobOutputTokens(job);
3688
+ const backendCost = backendCostEstimateForJob(job);
3443
3689
  const longContext = Boolean(priceEntry?.longContextPrice && priceEntry.longContextThreshold && input.inputTokens > priceEntry.longContextThreshold);
3444
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
+ }
3445
3704
  if (!price)
3446
- return { model, ...input, outputTokens, longContext: false, cost: 0 };
3705
+ return { model, ...input, outputTokens, longContext: false, backendEstimate: false, costKnown: false, cost: 0 };
3447
3706
  const cost = ((input.uncachedInputTokens * price.input) + (input.cachedInputTokens * price.cachedInput) + (outputTokens * price.output)) / 1_000_000;
3448
- 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);
3449
3714
  }
3450
3715
  function modelPriceEntryForModel(model) {
3451
3716
  const key = normalizedModelKey(model);
@@ -3473,7 +3738,7 @@ function inputTokenBreakdownForJob(job) {
3473
3738
  const inputDetails = recordValue(job.inputTokensDetails ?? job.input_tokens_details ?? usage.input_tokens_details);
3474
3739
  const promptDetails = recordValue(job.promptTokensDetails ?? job.prompt_tokens_details ?? usage.prompt_tokens_details);
3475
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);
3476
- 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);
3477
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);
3478
3743
  const reportedUncached = firstPositiveNumber(job.uncachedInputTokens, job.uncached_input_tokens, usage.uncached_input_tokens);
3479
3744
  const inputTokens = actualInput || estimatedInput || reportedCached + reportedUncached;