@yemi33/minions 0.1.2454 → 0.1.2455

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/bin/install-internal-minions.js +136 -6
  2. package/bin/minions.js +27 -13
  3. package/dashboard/js/agent-identity.js +121 -0
  4. package/dashboard/js/modal.js +4 -0
  5. package/dashboard/js/refresh.js +38 -3
  6. package/dashboard/js/render-agents.js +14 -2
  7. package/dashboard/js/render-dispatch.js +2 -2
  8. package/dashboard/js/render-other.js +147 -13
  9. package/dashboard/js/render-prd.js +6 -5
  10. package/dashboard/js/render-prs.js +1 -1
  11. package/dashboard/js/render-work-items.js +436 -21
  12. package/dashboard/js/utils.js +40 -0
  13. package/dashboard/pages/tools.html +1 -0
  14. package/dashboard/pages/work.html +13 -0
  15. package/dashboard/styles.css +73 -0
  16. package/dashboard-build.js +2 -2
  17. package/dashboard.js +93 -7
  18. package/docs/README.md +1 -0
  19. package/docs/copilot-cli-schema.md +1 -0
  20. package/docs/engine-restart.md +4 -2
  21. package/docs/internal-install.md +44 -5
  22. package/docs/named-agents.md +48 -0
  23. package/docs/runtime-adapters.md +39 -0
  24. package/docs/temporary-agents.md +172 -0
  25. package/engine/ado/comment.js +261 -4
  26. package/engine/agents/llm.js +26 -0
  27. package/engine/agents/playbook.js +2 -1
  28. package/engine/core/queries.js +27 -2
  29. package/engine/core/shared.js +148 -1
  30. package/engine/operations/cli.js +102 -1
  31. package/engine/orchestration/lifecycle.js +7 -0
  32. package/engine/orchestration/routing.js +4 -1
  33. package/engine/providers/gh-comment.js +159 -0
  34. package/engine/recovery/stop-stack.js +16 -2
  35. package/engine/runtimes/claude.js +3 -0
  36. package/engine/runtimes/codex.js +4 -0
  37. package/engine/runtimes/copilot.js +23 -2
  38. package/engine.js +2 -2
  39. package/package.json +1 -1
  40. package/playbooks/fix.md +23 -2
  41. package/playbooks/shared-rules.md +24 -2
@@ -702,13 +702,97 @@ function _formatTrackedCost(value, available) {
702
702
  return '$' + (Number(value) || 0).toFixed(2);
703
703
  }
704
704
 
705
+ // Registry of NON-USD runtime-reported billable units (W-msdcgitc). Generic so a
706
+ // future adapter can surface its own unit without touching call sites; the
707
+ // display layer keys off typed `m.billableUnit` metadata, never a runtime name.
708
+ var BILLABLE_UNIT_META = {
709
+ premiumRequests: { abbrev: 'req', label: 'premium requests', tileLabel: 'Premium Requests' },
710
+ };
711
+
712
+ function _billableBucket(m, unit) {
713
+ return (m && m.billableUnits && m.billableUnits[unit]) || null;
714
+ }
715
+
716
+ function _formatBillableUnitValue(unit, value) {
717
+ var meta = BILLABLE_UNIT_META[unit];
718
+ var abbr = meta ? meta.abbrev : unit;
719
+ var n = Number(value) || 0;
720
+ var num = Number.isInteger(n) ? String(n) : n.toFixed(1);
721
+ return num + ' ' + abbr;
722
+ }
723
+
724
+ function _hasReportedBillableUnits(m) {
725
+ if (!m || !m.billableUnits) return false;
726
+ for (var u in m.billableUnits) {
727
+ var b = m.billableUnits[u];
728
+ if (b && (b.reported || 0) > 0) return true;
729
+ }
730
+ return false;
731
+ }
732
+
733
+ function _firstReportedBillableUnit(m) {
734
+ if (!m || !m.billableUnits) return null;
735
+ for (var u2 in m.billableUnits) {
736
+ var b2 = m.billableUnits[u2];
737
+ if (b2 && (b2.reported || 0) > 0) return u2;
738
+ }
739
+ return null;
740
+ }
741
+
742
+ // Primary consumption cell for an agent row — the SAME UI position where a
743
+ // USD-billed runtime shows dollar cost. For 'usd' runtimes this is unchanged
744
+ // dollar rendering; for a native-unit runtime (e.g. Copilot premium requests)
745
+ // it shows the authoritative reported count, and 'N/A' when the CLI never
746
+ // reported the unit (distinct from a genuine reported 0). Switches on typed
747
+ // metadata (m.billableUnit), never the runtime name.
748
+ function _formatPrimaryUsage(m) {
749
+ var unit = m.billableUnit || 'usd';
750
+ if (unit === 'usd') {
751
+ return _formatTrackedCost(m.totalCostUsd, m.costTracking !== false);
752
+ }
753
+ var bucket = _billableBucket(m, unit);
754
+ if (!bucket || (bucket.reported || 0) === 0) return 'N/A';
755
+ return _formatBillableUnitValue(unit, bucket.total || 0);
756
+ }
757
+
758
+ function _formatPrimaryPerTask(m, tasks) {
759
+ var unit = m.billableUnit || 'usd';
760
+ if (unit === 'usd') {
761
+ var costAvailable = m.costTracking !== false;
762
+ return tasks > 0 ? _formatTrackedCost((m.totalCostUsd || 0) / tasks, costAvailable) : '-';
763
+ }
764
+ var bucket = _billableBucket(m, unit);
765
+ if (!bucket || (bucket.reported || 0) === 0) return 'N/A';
766
+ return tasks > 0 ? _formatBillableUnitValue(unit, (bucket.total || 0) / tasks) : '-';
767
+ }
768
+
769
+ // Primary consumption cell for an _engine category row (CC/doc-chat/…). USD wins
770
+ // when present; otherwise a reported native billable unit (e.g. Copilot premium
771
+ // requests for a Copilot-driven Command Center) renders in the SAME position,
772
+ // and 'N/A' when neither is reported. Typed off the data, never a runtime name.
773
+ function _formatEnginePrimaryUsage(e) {
774
+ if ((e.costUsd || 0) > 0) return _formatTrackedCost(e.costUsd, true);
775
+ var unit = _firstReportedBillableUnit(e);
776
+ if (unit) return _formatBillableUnitValue(unit, e.billableUnits[unit].total || 0);
777
+ return 'N/A';
778
+ }
779
+
780
+ function _formatEnginePrimaryPerCall(e, calls) {
781
+ if (calls <= 0) return '-';
782
+ if ((e.costUsd || 0) > 0) return _formatTrackedCost((e.costUsd || 0) / calls, true);
783
+ var unit = _firstReportedBillableUnit(e);
784
+ if (unit) return _formatBillableUnitValue(unit, (e.billableUnits[unit].total || 0) / calls);
785
+ return 'N/A';
786
+ }
787
+
705
788
  function _usageExists(m) {
706
789
  return !!((m.totalCostUsd || 0)
707
790
  || (m.totalInputTokens || 0)
708
791
  || (m.totalOutputTokens || 0)
709
792
  || (m.totalCacheRead || 0)
710
793
  || (m.tasksCompleted || 0)
711
- || (m.tasksErrored || 0));
794
+ || (m.tasksErrored || 0)
795
+ || _hasReportedBillableUnits(m));
712
796
  }
713
797
 
714
798
  function renderTokenUsage(metrics) {
@@ -737,7 +821,15 @@ function renderTokenUsage(metrics) {
737
821
  const totalOutput = agentOutput + engineOutput;
738
822
  const totalCache = agentCache + engineCache;
739
823
 
740
- if (totalCost === 0 && Object.keys(daily).length === 0) {
824
+ // Show the empty state only when there is genuinely no usage of ANY kind.
825
+ // Keying solely off USD cost + daily (the original guard) would wrongly hide a
826
+ // zero-USD fleet that still reported token usage or a native billable unit such
827
+ // as Copilot premium requests (W-msdcgitc). `_usageExists` already folds tokens,
828
+ // tasks, cost, and reported billable units.
829
+ const _anyAgentUsage = agents.some(([, m]) => _usageExists(m));
830
+ const _anyEngineUsage = engineCalls > 0 || engineCost > 0
831
+ || Object.values(engine).some(e => _hasReportedBillableUnits(e));
832
+ if (!_anyAgentUsage && !_anyEngineUsage && Object.keys(daily).length === 0) {
741
833
  el.innerHTML = '<p class="empty">No usage data yet. Token tracking starts on next agent completion.</p>';
742
834
  return;
743
835
  }
@@ -753,6 +845,37 @@ function renderTokenUsage(metrics) {
753
845
  html += '<div class="token-tile"><div class="token-tile-label">Output Tokens</div><div class="token-tile-value">' + fmtTokens(totalOutput) + '</div></div>';
754
846
  html += '<div class="token-tile"><div class="token-tile-label">Cache Reads</div><div class="token-tile-value">' + fmtTokens(totalCache) + '</div></div>';
755
847
 
848
+ // Non-USD runtime-reported billable units (e.g. Copilot premium requests),
849
+ // aggregated across agents + engine categories (excluding the agent-dispatch
850
+ // mirror so agent premium requests aren't double-counted). Driven by typed
851
+ // usage metadata (W-msdcgitc). Unreported (CLI never exposed the unit) renders
852
+ // 'N/A', distinct from a genuine reported 0.
853
+ var billableAgg = {};
854
+ var _foldBillable = function(src) {
855
+ if (!src || !src.billableUnits) return;
856
+ for (var fu in src.billableUnits) {
857
+ var fb = src.billableUnits[fu]; if (!fb) continue;
858
+ var t = billableAgg[fu] || { total: 0, reported: 0, unavailable: 0 };
859
+ t.total += fb.total || 0; t.reported += fb.reported || 0; t.unavailable += fb.unavailable || 0;
860
+ billableAgg[fu] = t;
861
+ }
862
+ };
863
+ for (const [, bm] of agents) _foldBillable(bm);
864
+ for (const ek in engine) {
865
+ if (ek === 'agent-dispatch' || ek.startsWith('test-') || ek.startsWith('_test')) continue;
866
+ _foldBillable(engine[ek]);
867
+ }
868
+ for (var bu in billableAgg) {
869
+ var bb = billableAgg[bu];
870
+ var bmeta = BILLABLE_UNIT_META[bu] || { tileLabel: bu, label: bu, abbrev: bu };
871
+ var breported = (bb.reported || 0) > 0;
872
+ var bval = breported ? _formatBillableUnitValue(bu, bb.total || 0) : 'N/A';
873
+ var bsub = breported
874
+ ? (bb.unavailable > 0 ? bb.unavailable + ' run(s) without data' : 'authoritative ' + bmeta.label)
875
+ : 'not reported by CLI';
876
+ html += '<div class="token-tile"><div class="token-tile-label">' + escHtml(bmeta.tileLabel) + '</div><div class="token-tile-value">' + escHtml(bval) + '</div><div class="token-tile-sub">' + escHtml(bsub) + '</div></div>';
877
+ }
878
+
756
879
  // Today's cost
757
880
  const today = new Date().toISOString().slice(0, 10);
758
881
  const todayData = daily[today];
@@ -787,7 +910,7 @@ function renderTokenUsage(metrics) {
787
910
  var tempAgents = agents.filter(function(a) { return a[0].startsWith('temp-'); });
788
911
  var tokenRows = permAgents.slice();
789
912
  if (tempAgents.length > 0) {
790
- var tempMerged = { totalCostUsd: 0, totalInputTokens: 0, totalOutputTokens: 0, totalCacheRead: 0, tasksCompleted: 0, tasksErrored: 0, model: '', costTracking: true };
913
+ var tempMerged = { totalCostUsd: 0, totalInputTokens: 0, totalOutputTokens: 0, totalCacheRead: 0, tasksCompleted: 0, tasksErrored: 0, model: '', costTracking: true, billableUnit: 'usd', billableUnits: {} };
791
914
  for (var ti = 0; ti < tempAgents.length; ti++) {
792
915
  var tm = tempAgents[ti][1];
793
916
  tempMerged.totalCostUsd += tm.totalCostUsd || 0;
@@ -798,26 +921,37 @@ function renderTokenUsage(metrics) {
798
921
  tempMerged.tasksErrored += tm.tasksErrored || 0;
799
922
  if (tm.costTracking === false) tempMerged.costTracking = false;
800
923
  if (!tempMerged.model && tm.model) tempMerged.model = tm.model;
924
+ // Carry the native billable unit forward (temp agents share the fleet
925
+ // default runtime) and merge reported billable buckets so a Copilot temp
926
+ // fleet still surfaces premium requests.
927
+ if (tm.billableUnit && tm.billableUnit !== 'usd') tempMerged.billableUnit = tm.billableUnit;
928
+ if (tm.billableUnits) {
929
+ for (var bku in tm.billableUnits) {
930
+ var sbk = tm.billableUnits[bku]; if (!sbk) continue;
931
+ var dbk = tempMerged.billableUnits[bku] || { total: 0, reported: 0, unavailable: 0 };
932
+ dbk.total += sbk.total || 0; dbk.reported += sbk.reported || 0; dbk.unavailable += sbk.unavailable || 0;
933
+ tempMerged.billableUnits[bku] = dbk;
934
+ }
935
+ }
801
936
  }
802
937
  tokenRows.push(['Temp Agents (' + tempAgents.length + ')', tempMerged]);
803
938
  }
804
939
  var agentsWithUsage = tokenRows.filter(function(a) { return _usageExists(a[1]); });
805
940
  if (agentsWithUsage.length > 0) {
806
941
  html += '<div style="font-size:var(--text-sm);color:var(--muted);margin:12px 0 4px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px">Agent Usage</div>';
807
- html += '<table class="token-agent-table"><thead><tr><th style="width:20%">Agent</th><th style="width:10%">Model</th><th style="width:14%">Cost</th><th style="width:14%">Input</th><th style="width:14%">Output</th><th style="width:14%">Cache</th><th style="width:14%">$/task</th></tr></thead><tbody>';
942
+ html += '<table class="token-agent-table"><thead><tr><th style="width:20%">Agent</th><th style="width:10%">Model</th><th style="width:14%">Usage</th><th style="width:14%">Input</th><th style="width:14%">Output</th><th style="width:14%">Cache</th><th style="width:14%">Per Task</th></tr></thead><tbody>';
808
943
  for (const [id, m] of agentsWithUsage.sort((a, b) => (b[1].totalCostUsd || 0) - (a[1].totalCostUsd || 0))) {
809
944
  const tasks = (m.tasksCompleted || 0) + (m.tasksErrored || 0);
810
- const costAvailable = m.costTracking !== false;
811
- const perTask = tasks > 0 ? _formatTrackedCost((m.totalCostUsd || 0) / tasks, costAvailable) : '-';
945
+ const perTask = _formatPrimaryPerTask(m, tasks);
812
946
  const modelLabel = (m.model || '').replace(/^claude-/, '').replace(/\[.*\]$/, '') || '-';
813
947
  html += '<tr>' +
814
948
  '<td style="font-weight:600">' + escHtml(id) + '</td>' +
815
949
  '<td style="color:var(--muted);font-size:var(--text-sm)">' + escHtml(modelLabel) + '</td>' +
816
- '<td>' + _formatTrackedCost(m.totalCostUsd, costAvailable) + '</td>' +
950
+ '<td>' + escHtml(_formatPrimaryUsage(m)) + '</td>' +
817
951
  '<td>' + fmtTokens(m.totalInputTokens || 0) + '</td>' +
818
952
  '<td>' + fmtTokens(m.totalOutputTokens || 0) + '</td>' +
819
953
  '<td>' + fmtTokens(m.totalCacheRead || 0) + '</td>' +
820
- '<td style="color:var(--muted)">' + perTask + '</td>' +
954
+ '<td style="color:var(--muted)">' + escHtml(perTask) + '</td>' +
821
955
  '</tr>';
822
956
  }
823
957
  html += '</tbody></table>';
@@ -828,14 +962,14 @@ function renderTokenUsage(metrics) {
828
962
  if (engineEntries.length > 0) {
829
963
  const labels = { 'consolidation': 'Consolidation', 'command-center': 'Command Center', 'doc-chat': 'Doc Chat', 'kb-sweep': 'KB Sweep', 'schedule-parse': 'Schedule Parse' };
830
964
  html += '<div style="font-size:var(--text-sm);color:var(--muted);margin:12px 0 4px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px">Engine Usage</div>';
831
- html += '<table class="token-agent-table"><thead><tr><th style="width:20%">Operation</th><th style="width:10%">Calls</th><th style="width:14%">Cost</th><th style="width:14%">Input</th><th style="width:14%">Output</th><th style="width:14%">Cache</th><th style="width:14%">$/call</th></tr></thead><tbody>';
965
+ html += '<table class="token-agent-table"><thead><tr><th style="width:20%">Operation</th><th style="width:10%">Calls</th><th style="width:14%">Usage</th><th style="width:14%">Input</th><th style="width:14%">Output</th><th style="width:14%">Cache</th><th style="width:14%">Per Call</th></tr></thead><tbody>';
832
966
  for (const [cat, e] of engineEntries.sort((a, b) => (b[1].costUsd || 0) - (a[1].costUsd || 0))) {
833
- const costAvailable = (e.costUsd || 0) > 0;
834
- const perCall = (e.calls || 0) > 0 ? _formatTrackedCost((e.costUsd || 0) / e.calls, costAvailable) : '-';
967
+ const calls = e.calls || 0;
968
+ const perCall = _formatEnginePrimaryPerCall(e, calls);
835
969
  html += '<tr>' +
836
970
  '<td style="font-weight:600">' + escHtml(labels[cat] || cat) + '</td>' +
837
971
  '<td>' + (e.calls || 0) + '</td>' +
838
- '<td>' + _formatTrackedCost(e.costUsd, costAvailable) + '</td>' +
972
+ '<td>' + escHtml(_formatEnginePrimaryUsage(e)) + '</td>' +
839
973
  '<td>' + fmtTokens(e.inputTokens || 0) + '</td>' +
840
974
  '<td>' + fmtTokens(e.outputTokens || 0) + '</td>' +
841
975
  '<td>' + fmtTokens(e.cacheRead || 0) + '</td>' +
@@ -1129,7 +1263,7 @@ function renderHarnessDiagnostics(diag) {
1129
1263
  }
1130
1264
  }
1131
1265
 
1132
- window.MinionsOther = { renderProjects, optimisticallyAddProject, projectChipRemove, renderMcpServers, renderCommands, renderHarnessDiagnostics, renderMetrics, renderLlmPerf, renderTokenUsage, _aggregateEngineUsageForTokenTile, _formatTrackedCost, openScanProjectsModal };
1266
+ window.MinionsOther = { renderProjects, optimisticallyAddProject, projectChipRemove, renderMcpServers, renderCommands, renderHarnessDiagnostics, renderMetrics, renderLlmPerf, renderTokenUsage, _aggregateEngineUsageForTokenTile, _formatTrackedCost, _formatPrimaryUsage, _formatPrimaryPerTask, _formatBillableUnitValue, openScanProjectsModal };
1133
1267
 
1134
1268
  // ─── keep_processes panel (W-mp68q6ke0010de68) ─────────────────────────────
1135
1269
  // Polls /api/keep-processes every refresh (engine page only) and renders a
@@ -317,8 +317,9 @@ function renderPrdProgress(prog) {
317
317
  const wi = wiById[workItemId] || wiById[i.id];
318
318
  const wiAgent = wi?.dispatched_to ? (agentData.find(a => a.id === wi.dispatched_to) || {}) : null;
319
319
  const wiLabel = _renderPrdWorkItemIdBadge(wi ? wi.id : workItemId, i.id);
320
- const agentLabel = wiAgent ? '<span style="font-size:var(--text-sm);color:var(--muted)" title="' + escHtml(wiAgent.name || wi.dispatched_to) + '">' +
321
- (wiAgent.emoji || '') + ' ' + escHtml(wiAgent.name || wi.dispatched_to) + '</span>' : '';
320
+ const wiAgentIdentity = { id: wi?.dispatched_to, name: wiAgent && wiAgent.name };
321
+ const agentLabel = wiAgent ? '<span style="font-size:var(--text-sm);color:var(--muted)" title="' + escHtml(MinionsAgentIdentity.displayName(wiAgentIdentity)) + '">' +
322
+ (wiAgent.emoji || '') + ' ' + MinionsAgentIdentity.nameWithBadgeHtml(wiAgentIdentity) + '</span>' : '';
322
323
 
323
324
  // Branch label — show the target branch for this item
324
325
  const wiBranch = wi ? (wi.branch || wi.featureBranch || (wi._artifacts && wi._artifacts.branch) || '') : '';
@@ -382,7 +383,7 @@ function renderPrdProgress(prog) {
382
383
  statusBadge(c.status) +
383
384
  '<span style="color:var(--muted);font-size:var(--text-sm)">' + escHtml(c.id) + '</span>' +
384
385
  '<span style="flex:1">' + escHtml((c.title || '').replace('Implement: ', '').slice(0, 60)) + '</span>' +
385
- (childAgent ? '<span style="font-size:var(--text-sm);color:var(--muted)">' + (childAgent.emoji || '') + ' ' + escHtml(childAgent.name || c.dispatched_to) + '</span>' : '') +
386
+ (childAgent ? '<span style="font-size:var(--text-sm);color:var(--muted)">' + (childAgent.emoji || '') + ' ' + MinionsAgentIdentity.nameWithBadgeHtml({ id: c.dispatched_to, name: childAgent.name }) + '</span>' : '') +
386
387
  '</div>';
387
388
  }).join('') +
388
389
  '</div>';
@@ -589,7 +590,7 @@ function renderPrdProgress(prog) {
589
590
  const wiForCard = wi[workItemId] || wi[i.id];
590
591
  const agentId = wiForCard?.dispatched_to || '';
591
592
  const agentInfo = agentId ? (agentData.find(a => a.id === agentId) || {}) : null;
592
- const agentDisplay = agentInfo ? (agentInfo.emoji || '') + ' ' + escHtml(agentInfo.name || agentId) : (agentId ? escHtml(agentId) : '');
593
+ const agentDisplay = agentInfo ? (agentInfo.emoji || '') + ' ' + MinionsAgentIdentity.nameWithBadgeHtml({ id: agentId, name: agentInfo.name }) : (agentId ? MinionsAgentIdentity.nameWithBadgeHtml({ id: agentId }) : '');
593
594
  const deps = (i.depends_on || []).join(', ');
594
595
  const wipAnim = i.status === 'in-progress' ? 'animation:prdWipPulse 2s infinite;' : '';
595
596
  const wiIdBadge = _renderPrdWorkItemIdBadge(wiForCard ? wiForCard.id : workItemId, i.id, '8px', '1px 4px');
@@ -951,7 +952,7 @@ async function prdItemEdit(source, itemId) {
951
952
  '<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">' +
952
953
  (wi ? '<a href="#" onclick="event.preventDefault();event.stopPropagation();openWorkItemDetail(\'' + escHtml(wi.id) + '\',\'' + escHtml(wi._source || '') + '\')" title="Open ' + escHtml(wi.id) + ' detail" style="font-size:var(--text-base);font-weight:700;color:' + statusColor + ';text-decoration:none;cursor:pointer" onmouseover="this.style.textDecoration=\'underline\'" onmouseout="this.style.textDecoration=\'none\'">' + statusLabel + ' &rarr;</a>' : '<span style="font-size:var(--text-base);font-weight:700;color:' + statusColor + '">' + statusLabel + '</span>') +
953
954
  typePillHtml +
954
- (agent ? '<span style="font-size:var(--text-base);color:var(--muted)">by ' + escHtml(agent) + '</span>' : '') +
955
+ (agent ? '<span style="font-size:var(--text-base);color:var(--muted)">by ' + MinionsAgentIdentity.nameWithBadgeHtml({ id: agent }) + '</span>' : '') +
955
956
  (completedAt ? '<span style="font-size:var(--text-sm);color:var(--muted)">' + escHtml(completedAt.slice(0, 16).replace('T', ' ')) + '</span>' : '') +
956
957
  '</div>' +
957
958
  (prLinks ? '<div style="font-size:var(--text-base);margin-bottom:6px">PR: ' + prLinks + '</div>' : '') +
@@ -656,7 +656,7 @@ function _renderPrDetail(pr) {
656
656
  author = '<div><strong style="color:var(--muted);font-size:var(--text-base)">Author:</strong> <span style="color:var(--muted)">Unknown</span></div>';
657
657
  }
658
658
  }
659
- const agent = pr.agent ? '<div><strong style="color:var(--muted);font-size:var(--text-base)">Minions agent:</strong> ' + escapeHtml(String(pr.agent)) + '</div>' : '';
659
+ const agent = pr.agent ? '<div><strong style="color:var(--muted);font-size:var(--text-base)">Minions agent:</strong> ' + MinionsAgentIdentity.nameWithBadgeHtml({ id: String(pr.agent) }) + '</div>' : '';
660
660
  const branch = pr.branch ? '<div><strong style="color:var(--muted);font-size:var(--text-base)">Branch:</strong> <code>' + escapeHtml(String(pr.branch)) + '</code></div>' : '';
661
661
  const created = pr.created ? '<div><strong style="color:var(--muted);font-size:var(--text-base)">Created:</strong> ' + escapeHtml(String(pr.created)) + '</div>' : '';
662
662
  const project = pr._project ? '<div><strong style="color:var(--muted);font-size:var(--text-base)">Project:</strong> ' + escapeHtml(String(pr._project)) + '</div>' : '';