@yemi33/minions 0.1.2062 → 0.1.2063
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/dashboard/js/modal.js
CHANGED
|
@@ -4,11 +4,6 @@ function closeModal() {
|
|
|
4
4
|
const modalEl = document.querySelector('#modal .modal');
|
|
5
5
|
if (modalEl) modalEl.classList.remove('modal-wide');
|
|
6
6
|
document.getElementById('modal').classList.remove('open');
|
|
7
|
-
// Clear the WI-detail auto-refresh tracker so renderWorkItems stops
|
|
8
|
-
// rewriting #modal-body each tick. Safe to do unconditionally — if the
|
|
9
|
-
// closed modal wasn't a WI detail, these vars were already null.
|
|
10
|
-
if (typeof _wiModalOpenId !== 'undefined') { _wiModalOpenId = null; }
|
|
11
|
-
if (typeof _wiModalHydratedFields !== 'undefined') { _wiModalHydratedFields = null; }
|
|
12
7
|
clearModalBackStack();
|
|
13
8
|
// Hide Q&A section (only shown for document modals)
|
|
14
9
|
document.getElementById('modal-qa').style.display = 'none';
|
|
@@ -69,18 +69,24 @@ function renderAgents(agents) {
|
|
|
69
69
|
`).join('');
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
async function openAgentDetail(id) {
|
|
73
|
+
const agent = agentData.find(a => a.id === id);
|
|
74
|
+
if (!agent) return;
|
|
75
|
+
currentAgentId = id;
|
|
76
|
+
currentTab = (agent.status === 'working') ? 'live' : 'thought-process';
|
|
77
|
+
|
|
78
|
+
// SEC-03 Phase A: Build the detail header via DOM + textContent instead of innerHTML.
|
|
79
|
+
// Emoji, name and role are all user-controlled fields; routing them through textContent
|
|
80
|
+
// guarantees no HTML interpretation even if the escape function were ever bypassed.
|
|
79
81
|
const nameEl = document.getElementById('detail-agent-name');
|
|
80
|
-
if (!nameEl) return;
|
|
81
82
|
const emojiSpan = document.createElement('span');
|
|
82
83
|
emojiSpan.style.fontSize = '22px';
|
|
83
84
|
emojiSpan.textContent = agent.emoji || '';
|
|
85
|
+
// Runtime tag \u2014 uses the inline-SVG logo from the same RUNTIME_TAGS map the
|
|
86
|
+
// card uses, so the visual is consistent. The container's user-controlled
|
|
87
|
+
// text fields stay on the textContent path; the SVG is a hardcoded literal
|
|
88
|
+
// from RUNTIME_TAGS keyed by the runtime string (server-controlled, finite
|
|
89
|
+
// set), so injecting via innerHTML on the icon-only span is safe.
|
|
84
90
|
const runtimeMeta = RUNTIME_TAGS[agent.runtime];
|
|
85
91
|
const runtimeSpan = document.createElement('span');
|
|
86
92
|
runtimeSpan.title = 'Runtime: ' + (runtimeMeta?.label || agent.runtime || 'unknown');
|
|
@@ -94,6 +100,9 @@ function _renderAgentDetailHeader(agent) {
|
|
|
94
100
|
runtimeSpan.style.cssText += ';font-size:10px;font-weight:600;letter-spacing:0.4px;text-transform:uppercase;padding:2px 6px;border:1px solid var(--muted);border-radius:3px;color:var(--muted)';
|
|
95
101
|
runtimeSpan.textContent = agent.runtime || 'unknown';
|
|
96
102
|
}
|
|
103
|
+
// W-mpmwxk4y00053271 — mirror the model chip the card shows so the detail
|
|
104
|
+
// header stays in sync. textContent path keeps the model string from being
|
|
105
|
+
// interpreted as HTML.
|
|
97
106
|
const modelSpan = (agent.model && typeof agent.model === 'string')
|
|
98
107
|
? (() => {
|
|
99
108
|
const s = document.createElement('span');
|
|
@@ -112,27 +121,12 @@ function _renderAgentDetailHeader(agent) {
|
|
|
112
121
|
nameEl.replaceChildren(...children);
|
|
113
122
|
|
|
114
123
|
const badgeClass = agent.status;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
(agent._blockingToolCall ? '<div style="margin-top:4px;padding:4px 8px;background:rgba(130,160,210,0.13);border:1px solid rgba(130,160,210,0.3);border-radius:4px;font-size:11px;color:var(--muted)">⏳ Blocking tool call (' + escapeHtml(agent._blockingToolCall.tool) + ') — silent ' + Math.round(agent._blockingToolCall.silentMs/60000) + 'min, timeout in ' + Math.round(agent._blockingToolCall.remainingMs/60000) + 'min</div>' : '') +
|
|
122
|
-
(agent.resultSummary ? '<div style="margin-top:4px;font-size:11px;color:var(--text);line-height:1.4">' + renderMd(agent.resultSummary.slice(0, 300)) + '</div>' : '');
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async function openAgentDetail(id) {
|
|
127
|
-
const agent = agentData.find(a => a.id === id);
|
|
128
|
-
if (!agent) return;
|
|
129
|
-
currentAgentId = id;
|
|
130
|
-
currentTab = (agent.status === 'working') ? 'live' : 'thought-process';
|
|
131
|
-
|
|
132
|
-
// SEC-03 Phase A: Build the detail header via DOM + textContent instead of innerHTML.
|
|
133
|
-
// Emoji, name and role are all user-controlled fields; routing them through textContent
|
|
134
|
-
// guarantees no HTML interpretation even if the escape function were ever bypassed.
|
|
135
|
-
_renderAgentDetailHeader(agent);
|
|
124
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; status is an internal bounded enum and all user data is wrapped in escapeHtml()/renderMd() (fields: lastAction, blocking tool, resultSummary)
|
|
125
|
+
document.getElementById('detail-status-line').innerHTML =
|
|
126
|
+
'<span class="status-badge ' + badgeClass + '">' + agent.status.toUpperCase() + '</span> ' +
|
|
127
|
+
'<span style="color:var(--muted)">' + escapeHtml(agent.lastAction) + '</span>' +
|
|
128
|
+
(agent._blockingToolCall ? '<div style="margin-top:4px;padding:4px 8px;background:rgba(130,160,210,0.13);border:1px solid rgba(130,160,210,0.3);border-radius:4px;font-size:11px;color:var(--muted)">⏳ Blocking tool call (' + escapeHtml(agent._blockingToolCall.tool) + ') — silent ' + Math.round(agent._blockingToolCall.silentMs/60000) + 'min, timeout in ' + Math.round(agent._blockingToolCall.remainingMs/60000) + 'min</div>' : '') +
|
|
129
|
+
(agent.resultSummary ? '<div style="margin-top:4px;font-size:11px;color:var(--text);line-height:1.4">' + renderMd(agent.resultSummary.slice(0, 300)) + '</div>' : '');
|
|
136
130
|
|
|
137
131
|
// Show panel immediately with loading state — don't wait for API
|
|
138
132
|
document.getElementById('detail-content').innerHTML = '<div style="padding:24px;text-align:center;color:var(--muted)">Loading...</div>';
|
|
@@ -167,12 +161,7 @@ function _tickAgentRuntimes() {
|
|
|
167
161
|
el.textContent = 'Running: ' + (hr > 0 ? hr + 'h ' : '') + min + 'm ' + sec + 's';
|
|
168
162
|
});
|
|
169
163
|
}
|
|
170
|
-
// Start ticker after each render if working agents exist
|
|
171
|
-
// open agent-detail panel header so status / lastAction / blocking-tool /
|
|
172
|
-
// resultSummary track the latest /api/status slice without re-fetching the
|
|
173
|
-
// expensive /api/agent/<id> charter/history/output payload. The body tabs
|
|
174
|
-
// (thought-process, live, output, etc.) are loaded once on open via
|
|
175
|
-
// openAgentDetail's safeFetch — they stay as-is until the user reopens.
|
|
164
|
+
// Start ticker after each render if working agents exist
|
|
176
165
|
var _origRenderAgents = renderAgents;
|
|
177
166
|
renderAgents = function(agents) {
|
|
178
167
|
_origRenderAgents(agents);
|
|
@@ -181,10 +170,6 @@ renderAgents = function(agents) {
|
|
|
181
170
|
_tickAgentRuntimes();
|
|
182
171
|
_agentRuntimeTimer = setInterval(_tickAgentRuntimes, 1000);
|
|
183
172
|
}
|
|
184
|
-
if (currentAgentId) {
|
|
185
|
-
var open = agents.find(function(a) { return a.id === currentAgentId; });
|
|
186
|
-
if (open) _renderAgentDetailHeader(open);
|
|
187
|
-
}
|
|
188
173
|
};
|
|
189
174
|
|
|
190
175
|
window.MinionsAgents = { renderAgents, openAgentDetail };
|
|
@@ -4,18 +4,6 @@ let allWorkItems = [];
|
|
|
4
4
|
let wiPage = 0;
|
|
5
5
|
const WI_PER_PAGE = 20;
|
|
6
6
|
|
|
7
|
-
// Track open WI detail modal so renderWorkItems can re-render its body
|
|
8
|
-
// every poll tick. Without this the modal stays frozen on the snapshot
|
|
9
|
-
// from the moment it was opened — status flips / agent assignments /
|
|
10
|
-
// PR link arrival are invisible until the user closes + reopens or
|
|
11
|
-
// hard-refreshes. Mirrors the same fix that drove the section-render
|
|
12
|
-
// gate sweep (8ad48509 / W-mpn7keq9000302c9). `_wiModalHydratedFields`
|
|
13
|
-
// caches the heavy free-text fields (description, acceptanceCriteria,
|
|
14
|
-
// references) loaded once by openWorkItemDetail's GET /api/work-items/<id>
|
|
15
|
-
// hydration call so per-tick re-renders don't lose them.
|
|
16
|
-
let _wiModalOpenId = null;
|
|
17
|
-
let _wiModalHydratedFields = null;
|
|
18
|
-
|
|
19
7
|
// Track retry state per work item so loading/success/error survives re-renders
|
|
20
8
|
const _wiRetryState = {}; // { [id]: { status: 'pending'|'done'|'error', message?, until? } }
|
|
21
9
|
function setWiRetryState(id, state) { _wiRetryState[id] = state; }
|
|
@@ -167,27 +155,6 @@ function renderWorkItems(items) {
|
|
|
167
155
|
const newWrap = el.querySelector('.pr-table-wrap');
|
|
168
156
|
if (newWrap) newWrap.scrollLeft = savedScroll;
|
|
169
157
|
}
|
|
170
|
-
// Refresh the open WI detail modal in-place so its status badge,
|
|
171
|
-
// agent assignment, PR link, etc. reflect the latest /api/status slice.
|
|
172
|
-
// The heavy free-text fields (description, AC, references) live in
|
|
173
|
-
// `_wiModalHydratedFields` from the one-time GET /api/work-items/<id>
|
|
174
|
-
// hydration and survive across these re-renders.
|
|
175
|
-
if (_wiModalOpenId) {
|
|
176
|
-
const slim = items.find(i => i.id === _wiModalOpenId);
|
|
177
|
-
if (slim) {
|
|
178
|
-
const merged = Object.assign({}, _wiModalHydratedFields || {}, slim);
|
|
179
|
-
const body = document.getElementById('modal-body');
|
|
180
|
-
if (body) {
|
|
181
|
-
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() or renderMd() by _wiRenderDetail()
|
|
182
|
-
body.innerHTML = _wiRenderDetail(merged);
|
|
183
|
-
}
|
|
184
|
-
const title = document.getElementById('modal-title');
|
|
185
|
-
if (title) title.textContent = slim.title || slim.id;
|
|
186
|
-
}
|
|
187
|
-
// If the WI dropped out of the slim slice (deleted/archived), leave
|
|
188
|
-
// the modal as-is — the user will close it normally; we don't want
|
|
189
|
-
// to auto-dismiss in case they're still reading.
|
|
190
|
-
}
|
|
191
158
|
}
|
|
192
159
|
|
|
193
160
|
async function editWorkItem(id, source) {
|
|
@@ -682,15 +649,6 @@ function openWorkItemDetail(id) {
|
|
|
682
649
|
(cached.referencesCount > 0 && !Array.isArray(cached.references));
|
|
683
650
|
|
|
684
651
|
const initial = needsHydration ? Object.assign({}, cached, { _descriptionLoading: true }) : cached;
|
|
685
|
-
// Track which WI's modal is open so renderWorkItems can re-render the
|
|
686
|
-
// modal body each poll tick. Reset the hydrated cache; openWorkItemDetail
|
|
687
|
-
// is the only authoritative source of the heavy free-text fields.
|
|
688
|
-
_wiModalOpenId = id;
|
|
689
|
-
_wiModalHydratedFields = needsHydration ? null : {
|
|
690
|
-
description: cached.description,
|
|
691
|
-
acceptanceCriteria: Array.isArray(cached.acceptanceCriteria) ? cached.acceptanceCriteria : undefined,
|
|
692
|
-
references: Array.isArray(cached.references) ? cached.references : undefined,
|
|
693
|
-
};
|
|
694
652
|
document.getElementById('modal-title').textContent = initial.title || initial.id;
|
|
695
653
|
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() or renderMd() by _wiRenderDetail() (fields: title, description, agent, source, reasons, references, artifacts, PR links)
|
|
696
654
|
document.getElementById('modal-body').innerHTML = _wiRenderDetail(initial);
|
|
@@ -704,22 +662,18 @@ function openWorkItemDetail(id) {
|
|
|
704
662
|
.then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
|
|
705
663
|
.then(function(data) {
|
|
706
664
|
// Guard against modal navigation away from this WI during the fetch.
|
|
707
|
-
|
|
665
|
+
var title = document.getElementById('modal-title');
|
|
666
|
+
if (!title || title.textContent !== (initial.title || initial.id)) return;
|
|
708
667
|
var full = data && data.item;
|
|
709
668
|
if (!full) return;
|
|
710
|
-
// Cache the heavy free-text fields so per-tick re-renders preserve
|
|
711
|
-
// them. We keep them separate from the slim slice (which renderWorkItems
|
|
712
|
-
// refreshes on every tick).
|
|
713
|
-
_wiModalHydratedFields = {
|
|
714
|
-
description: full.description || cached.description || '',
|
|
715
|
-
acceptanceCriteria: Array.isArray(full.acceptanceCriteria) ? full.acceptanceCriteria : undefined,
|
|
716
|
-
references: Array.isArray(full.references) ? full.references : undefined,
|
|
717
|
-
};
|
|
718
669
|
// Merge: cached cross-slice fields (_pr, _artifacts, etc.) WIN over
|
|
719
670
|
// the on-disk record so we don't lose engine enrichment that lives
|
|
720
671
|
// only on the in-memory pass. The full record contributes description,
|
|
721
672
|
// acceptanceCriteria, and references back to the rendered shape.
|
|
722
|
-
var merged = Object.assign({}, full, cached
|
|
673
|
+
var merged = Object.assign({}, full, cached);
|
|
674
|
+
merged.description = full.description || cached.description || '';
|
|
675
|
+
if (Array.isArray(full.acceptanceCriteria)) merged.acceptanceCriteria = full.acceptanceCriteria;
|
|
676
|
+
if (Array.isArray(full.references)) merged.references = full.references;
|
|
723
677
|
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() or renderMd() by _wiRenderDetail() (fields: title, description, agent, source, reasons, references, artifacts, PR links)
|
|
724
678
|
document.getElementById('modal-body').innerHTML = _wiRenderDetail(merged);
|
|
725
679
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2063",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|