@yemi33/minions 0.1.2453 → 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.
- package/bin/install-internal-minions.js +136 -6
- package/bin/minions.js +27 -13
- package/dashboard/js/agent-identity.js +121 -0
- package/dashboard/js/modal.js +4 -0
- package/dashboard/js/refresh.js +38 -3
- package/dashboard/js/render-agents.js +14 -2
- package/dashboard/js/render-dispatch.js +2 -2
- package/dashboard/js/render-other.js +147 -13
- package/dashboard/js/render-prd.js +6 -5
- package/dashboard/js/render-prs.js +44 -25
- package/dashboard/js/render-work-items.js +442 -22
- package/dashboard/js/settings.js +8 -0
- package/dashboard/js/utils.js +40 -0
- package/dashboard/pages/tools.html +1 -0
- package/dashboard/pages/work.html +13 -0
- package/dashboard/shared/pr-author.js +75 -0
- package/dashboard/shared/pr-filters.js +28 -5
- package/dashboard/styles.css +85 -0
- package/dashboard-build.js +3 -3
- package/dashboard.js +93 -7
- package/docs/README.md +1 -0
- package/docs/copilot-cli-schema.md +1 -0
- package/docs/engine-restart.md +4 -2
- package/docs/internal-install.md +44 -5
- package/docs/named-agents.md +48 -0
- package/docs/pr-author-identity.md +63 -10
- package/docs/runtime-adapters.md +39 -0
- package/docs/temporary-agents.md +172 -0
- package/engine/ado/comment.js +261 -4
- package/engine/agents/llm.js +26 -0
- package/engine/agents/playbook.js +2 -1
- package/engine/api/settings-validation.js +25 -0
- package/engine/core/operator-identity.js +23 -1
- package/engine/core/queries.js +53 -2
- package/engine/core/shared.js +286 -9
- package/engine/db/migrations/032-review-enrolled-pr-context-only.js +95 -0
- package/engine/operations/cli.js +102 -1
- package/engine/orchestration/lifecycle.js +7 -0
- package/engine/orchestration/routing.js +4 -1
- package/engine/providers/gh-comment.js +159 -0
- package/engine/recovery/stop-stack.js +16 -2
- package/engine/runtimes/claude.js +3 -0
- package/engine/runtimes/codex.js +4 -0
- package/engine/runtimes/copilot.js +23 -2
- package/engine.js +2 -2
- package/package.json +1 -1
- package/playbooks/fix.md +23 -2
- 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
|
-
|
|
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%">
|
|
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
|
|
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>' +
|
|
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%">
|
|
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
|
|
834
|
-
const perCall = (e
|
|
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>' +
|
|
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
|
|
321
|
-
|
|
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 || '') + ' ' +
|
|
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 || '') + ' ' +
|
|
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 + ' →</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 ' +
|
|
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>' : '') +
|
|
@@ -150,31 +150,50 @@ function prAutomationToggle(idMatch, automation, label, enabled) {
|
|
|
150
150
|
// renders "Unknown" rather than borrowing the agent name. Links to the provider
|
|
151
151
|
// profile only when a validated http(s) URL is present.
|
|
152
152
|
function _prAuthorCellHtml(pr) {
|
|
153
|
-
var
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
var
|
|
157
|
-
if (a && typeof a === 'object') {
|
|
158
|
-
login = a.login || '';
|
|
159
|
-
displayName = a.displayName || '';
|
|
160
|
-
url = a.url || '';
|
|
161
|
-
} else if (typeof a === 'string') {
|
|
162
|
-
login = a;
|
|
163
|
-
}
|
|
164
|
-
var primary = displayName || login;
|
|
153
|
+
var info = (window.MinionsPrAuthor && window.MinionsPrAuthor.display)
|
|
154
|
+
? window.MinionsPrAuthor.display(pr ? pr.author : null)
|
|
155
|
+
: { name: '', handle: '', url: '' };
|
|
156
|
+
var primary = info.name;
|
|
165
157
|
if (!primary) {
|
|
166
158
|
return '<span style="color:var(--muted);font-size:var(--text-base)" title="Author unavailable">Unknown</span>';
|
|
167
159
|
}
|
|
168
|
-
var secondary =
|
|
160
|
+
var secondary = info.handle;
|
|
169
161
|
var inner = escapeHtml(primary)
|
|
170
162
|
+ (secondary ? ' <span style="color:var(--muted);font-size:var(--text-base)">@' + escapeHtml(secondary) + '</span>' : '');
|
|
171
163
|
var titleText = secondary ? primary + ' (' + secondary + ')' : primary;
|
|
172
|
-
if (
|
|
173
|
-
return '<a class="pr-agent" title="' + escapeHtml(titleText) + '" href="' + escapeHtml(safeUrl(url)) + '" target="_blank" rel="noopener">' + inner + '</a>';
|
|
164
|
+
if (info.url) {
|
|
165
|
+
return '<a class="pr-agent" title="' + escapeHtml(titleText) + '" href="' + escapeHtml(safeUrl(info.url)) + '" target="_blank" rel="noopener">' + inner + '</a>';
|
|
174
166
|
}
|
|
175
167
|
return '<span class="pr-agent" title="' + escapeHtml(titleText) + '">' + inner + '</span>';
|
|
176
168
|
}
|
|
177
169
|
|
|
170
|
+
// W-msd8pr2300ev95e1 — Foreign-author guardrail for the AutoFix column. When
|
|
171
|
+
// auto-fix is ENABLED and the PR was NOT authored by the current authenticated
|
|
172
|
+
// identity for that provider, render a prominent warning so the operator sees
|
|
173
|
+
// that Minions may push commits to someone else's PR. Ownership tri-state is
|
|
174
|
+
// stamped engine-side (pr._authorOwnership) from STABLE provider identities and
|
|
175
|
+
// fails CLOSED: 'unknown' shows an "author ownership unknown" warning rather
|
|
176
|
+
// than silently treating the PR as self-authored. Never warns when auto-fix is
|
|
177
|
+
// off (no automatic pushes happen) or when ownership is 'self'.
|
|
178
|
+
function _prAutoFixForeignAuthorWarning(pr, autoFixEnabled) {
|
|
179
|
+
if (!autoFixEnabled) return '';
|
|
180
|
+
var ownership = pr && pr._authorOwnership;
|
|
181
|
+
if (ownership === 'self') return '';
|
|
182
|
+
var isForeign = ownership === 'foreign';
|
|
183
|
+
// Author name is presentation-only (never an email) via the shared formatter.
|
|
184
|
+
var authorName = (window.MinionsPrAuthor && window.MinionsPrAuthor.name)
|
|
185
|
+
? window.MinionsPrAuthor.name(pr ? pr.author : null) : '';
|
|
186
|
+
var label = isForeign
|
|
187
|
+
? (authorName
|
|
188
|
+
? 'Auto-fix enabled for another author\'s PR (author: ' + authorName + ')'
|
|
189
|
+
: 'Auto-fix enabled for another author\'s PR')
|
|
190
|
+
: 'Auto-fix enabled but author ownership unknown';
|
|
191
|
+
var cls = 'pr-autofix-warning' + (isForeign ? ' pr-autofix-warning--foreign' : ' pr-autofix-warning--unknown');
|
|
192
|
+
return '<span class="' + cls + '" role="img" '
|
|
193
|
+
+ 'title="' + escapeHtml(label) + '" '
|
|
194
|
+
+ 'aria-label="' + escapeHtml(label) + '">\u26A0</span>';
|
|
195
|
+
}
|
|
196
|
+
|
|
178
197
|
function prRow(pr) {
|
|
179
198
|
// Minions review (agent) state — separate from ADO human review
|
|
180
199
|
const sq = pr.minionsReview || {};
|
|
@@ -325,6 +344,7 @@ function prRow(pr) {
|
|
|
325
344
|
: pr.contextOnly !== true;
|
|
326
345
|
var autoFixBtn = prAutomationToggle(idMatch, 'fix', 'Auto Fix', autoFixEnabled);
|
|
327
346
|
var autoReviewBtn = prAutomationToggle(idMatch, 'review', 'Auto Review', autoReviewEnabled);
|
|
347
|
+
var autoFixWarning = _prAutoFixForeignAuthorWarning(pr, autoFixEnabled);
|
|
328
348
|
// Title attrs live on the inner element (link/span/badge) so hovering the
|
|
329
349
|
// ellipsis-truncated content reveals the full text. Cell tags stay bare so
|
|
330
350
|
// the header-to-cell count assertion in test/unit.test.js continues to
|
|
@@ -348,7 +368,7 @@ function prRow(pr) {
|
|
|
348
368
|
'<td><span class="' + branchClass + '" title="' + escapeHtml(branchError || branchLabel) + '">' + escapeHtml(branchLabel) + '</span>' + pendingReasonHtml + '</td>' +
|
|
349
369
|
'<td>' + authorCellHtml + '</td>' +
|
|
350
370
|
'<td>' + reviewerCell + '</td>' +
|
|
351
|
-
'<td>' + (autoFixBtn
|
|
371
|
+
'<td>' + (autoFixBtn ? autoFixBtn + autoFixWarning : '<span style="color:var(--muted);font-size:var(--text-base)">—</span>') + '</td>' +
|
|
352
372
|
'<td>' + (autoReviewBtn || '<span style="color:var(--muted);font-size:var(--text-base)">—</span>') + '</td>' +
|
|
353
373
|
'<td><span class="pr-date" title="' + escapeHtml(createdLabel) + '">' + escapeHtml(createdLabel) + '</span></td>' +
|
|
354
374
|
'<td><button class="btn-destructive" style="padding:1px 5px" data-pr-id="' + escapeHtml(String(prId)) + '" onclick="event.stopPropagation();unlinkPr(this.dataset.prId)" title="Remove from tracking">x</button></td>' +
|
|
@@ -622,22 +642,21 @@ function _renderPrDetail(pr) {
|
|
|
622
642
|
// so operators can answer both "who is this for?" and "which agent handled it?".
|
|
623
643
|
let author = '';
|
|
624
644
|
{
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
const primary = displayName || login;
|
|
645
|
+
const info = (window.MinionsPrAuthor && window.MinionsPrAuthor.display)
|
|
646
|
+
? window.MinionsPrAuthor.display(pr.author)
|
|
647
|
+
: { name: '', handle: '', url: '' };
|
|
648
|
+
const primary = info.name;
|
|
630
649
|
if (primary) {
|
|
631
|
-
const secondary =
|
|
632
|
-
const valHtml =
|
|
633
|
-
? '<a href="' + escapeHtml(safeUrl(url)) + '" target="_blank" rel="noopener">' + escapeHtml(primary) + '</a>' + secondary
|
|
650
|
+
const secondary = info.handle ? ' <span style="color:var(--muted)">@' + escapeHtml(info.handle) + '</span>' : '';
|
|
651
|
+
const valHtml = info.url
|
|
652
|
+
? '<a href="' + escapeHtml(safeUrl(info.url)) + '" target="_blank" rel="noopener">' + escapeHtml(primary) + '</a>' + secondary
|
|
634
653
|
: escapeHtml(primary) + secondary;
|
|
635
654
|
author = '<div><strong style="color:var(--muted);font-size:var(--text-base)">Author:</strong> ' + valHtml + '</div>';
|
|
636
655
|
} else {
|
|
637
656
|
author = '<div><strong style="color:var(--muted);font-size:var(--text-base)">Author:</strong> <span style="color:var(--muted)">Unknown</span></div>';
|
|
638
657
|
}
|
|
639
658
|
}
|
|
640
|
-
const agent = pr.agent ? '<div><strong style="color:var(--muted);font-size:var(--text-base)">Minions agent:</strong> ' +
|
|
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>' : '';
|
|
641
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>' : '';
|
|
642
661
|
const created = pr.created ? '<div><strong style="color:var(--muted);font-size:var(--text-base)">Created:</strong> ' + escapeHtml(String(pr.created)) + '</div>' : '';
|
|
643
662
|
const project = pr._project ? '<div><strong style="color:var(--muted);font-size:var(--text-base)">Project:</strong> ' + escapeHtml(String(pr._project)) + '</div>' : '';
|