@yemi33/minions 0.1.363 → 0.1.365
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/CHANGELOG.md +10 -0
- package/dashboard/js/refresh.js +1 -1
- package/dashboard/js/render-agents.js +5 -5
- package/dashboard/js/render-inbox.js +4 -4
- package/dashboard/js/render-pinned.js +5 -5
- package/dashboard/js/render-prd.js +5 -5
- package/dashboard/js/render-prs.js +2 -2
- package/dashboard/js/state.js +1 -0
- package/dashboard/js/utils.js +7 -0
- package/dashboard.js +5 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.365 (2026-04-06)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
- XSS vulnerabilities across dashboard components (#211)
|
|
7
|
+
|
|
8
|
+
## 0.1.364 (2026-04-06)
|
|
9
|
+
|
|
10
|
+
### Fixes
|
|
11
|
+
- critical dashboard bugs — plan pause, PRD tautology, inbox null guard (#210)
|
|
12
|
+
|
|
3
13
|
## 0.1.363 (2026-04-06)
|
|
4
14
|
|
|
5
15
|
### Features
|
package/dashboard/js/refresh.js
CHANGED
|
@@ -41,7 +41,7 @@ function _processStatusUpdate(data) {
|
|
|
41
41
|
renderAgents(data.agents);
|
|
42
42
|
renderPrdProgress(data.prdProgress);
|
|
43
43
|
_cachePrdItems(data.prdProgress);
|
|
44
|
-
renderInbox(data.inbox);
|
|
44
|
+
renderInbox(data.inbox || []);
|
|
45
45
|
cmdUpdateAgentList(data.agents);
|
|
46
46
|
cmdUpdateProjectList(data.projects || []);
|
|
47
47
|
renderNotes(data.notes);
|
|
@@ -4,12 +4,12 @@ function renderAgents(agents) {
|
|
|
4
4
|
agentData = agents;
|
|
5
5
|
const grid = document.getElementById('agents-grid');
|
|
6
6
|
grid.innerHTML = agents.map(a => `
|
|
7
|
-
<div class="agent-card ${statusColor(a.status)}" onclick="openAgentDetail('${a.id}')">
|
|
7
|
+
<div class="agent-card ${statusColor(a.status)}" onclick="openAgentDetail('${escHtml(a.id)}')">
|
|
8
8
|
<div class="agent-card-header">
|
|
9
|
-
<span class="agent-name"><span class="agent-emoji">${a.emoji}</span>${a.name}</span>
|
|
10
|
-
<span class="status-badge ${a.status}">${a.status}</span>
|
|
9
|
+
<span class="agent-name"><span class="agent-emoji">${escHtml(a.emoji)}</span>${escHtml(a.name)}</span>
|
|
10
|
+
<span class="status-badge ${escHtml(a.status)}">${escHtml(a.status)}</span>
|
|
11
11
|
</div>
|
|
12
|
-
<div class="agent-role">${a.role}</div>
|
|
12
|
+
<div class="agent-role">${escHtml(a.role)}</div>
|
|
13
13
|
<div class="agent-action" title="${escHtml(a.lastAction)}">${escHtml(a.lastAction)}</div>
|
|
14
14
|
${a._warning ? `<div style="margin-top:4px;padding:4px 8px;background:rgba(210,153,34,0.15);border:1px solid rgba(210,153,34,0.3);border-radius:4px;font-size:10px;color:var(--yellow)">⚠ ${escHtml(a._warning)}</div>` : ''}
|
|
15
15
|
${a._permissionMode && a._permissionMode !== 'bypassPermissions' && !a._warning ? `<div style="margin-top:4px;font-size:9px;color:var(--muted)">Permission mode: ${escHtml(a._permissionMode)}</div>` : ''}
|
|
@@ -25,7 +25,7 @@ async function openAgentDetail(id) {
|
|
|
25
25
|
currentTab = (agent.status === 'working') ? 'live' : 'thought-process';
|
|
26
26
|
|
|
27
27
|
document.getElementById('detail-agent-name').innerHTML =
|
|
28
|
-
'<span style="font-size:22px">' + agent.emoji + '</span> ' + agent.name + ' — ' + agent.role;
|
|
28
|
+
'<span style="font-size:22px">' + escHtml(agent.emoji) + '</span> ' + escHtml(agent.name) + ' — ' + escHtml(agent.role);
|
|
29
29
|
|
|
30
30
|
const badgeClass = agent.status;
|
|
31
31
|
document.getElementById('detail-status-line').innerHTML =
|
|
@@ -24,13 +24,13 @@ function renderInbox(inbox) {
|
|
|
24
24
|
const idx = inboxStart + i;
|
|
25
25
|
return `<div class="inbox-item" data-file="notes/inbox/${escHtml(item.name)}">
|
|
26
26
|
<div class="inbox-name" onclick="openModal(${idx})" style="cursor:pointer">
|
|
27
|
-
<span>${escHtml(item.name)}</span><span>${item.age}</span>
|
|
27
|
+
<span>${escHtml(item.name)}</span><span>${escHtml(item.age || '')}</span>
|
|
28
28
|
</div>
|
|
29
29
|
<div class="inbox-preview" onclick="openModal(${idx})" style="cursor:pointer">${escHtml(item.content.slice(0,200))}</div>
|
|
30
30
|
<div style="display:flex;gap:6px;margin-top:6px;align-items:center">
|
|
31
|
-
<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" onclick="event.stopPropagation();promoteToKB(
|
|
32
|
-
<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" onclick="event.stopPropagation();openInboxInExplorer(
|
|
33
|
-
<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--red)" onclick="event.stopPropagation();deleteInboxItem(
|
|
31
|
+
<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" data-inbox-name="${escHtml(item.name)}" onclick="event.stopPropagation();promoteToKB(this.dataset.inboxName)">Add to Knowledge Base</button>
|
|
32
|
+
<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" data-inbox-name="${escHtml(item.name)}" onclick="event.stopPropagation();openInboxInExplorer(this.dataset.inboxName)">Open in Explorer</button>
|
|
33
|
+
<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--red)" data-inbox-name="${escHtml(item.name)}" onclick="event.stopPropagation();deleteInboxItem(this.dataset.inboxName)">Delete</button>
|
|
34
34
|
</div>
|
|
35
35
|
</div>`;
|
|
36
36
|
}).join('');
|
|
@@ -14,7 +14,7 @@ function renderPinned(entries) {
|
|
|
14
14
|
';border-radius:4px">' +
|
|
15
15
|
'<div style="display:flex;justify-content:space-between;align-items:center">' +
|
|
16
16
|
'<strong style="font-size:var(--text-md)">' + escHtml(e.title) + '</strong>' +
|
|
17
|
-
'<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--red);border-color:var(--red)"
|
|
17
|
+
'<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--red);border-color:var(--red)" data-pin-title="' + escHtml(e.title) + '" onclick="removePinnedNote(this.dataset.pinTitle)">Unpin</button>' +
|
|
18
18
|
'</div>' +
|
|
19
19
|
'<div style="font-size:var(--text-sm);color:var(--muted);margin-top:4px">' + renderMd(e.content.slice(0, 200)) + '</div>' +
|
|
20
20
|
'</div>'
|
|
@@ -28,13 +28,13 @@ function openPinNoteModal() {
|
|
|
28
28
|
'<label style="color:var(--text);font-size:var(--text-md)">Title<input id="pin-title" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text)" placeholder="e.g. API freeze until Friday"></label>' +
|
|
29
29
|
'<label style="color:var(--text);font-size:var(--text-md)">Content<textarea id="pin-content" rows="4" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);resize:vertical;font-family:inherit" placeholder="Context all agents should see..."></textarea></label>' +
|
|
30
30
|
'<label style="color:var(--text);font-size:var(--text-md)">Level<select id="pin-level" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text)"><option value="info">Info</option><option value="warning">Warning</option><option value="critical">Critical</option></select></label>' +
|
|
31
|
-
'<div style="display:flex;justify-content:flex-end;gap:8px"><button onclick="closeModal()" class="pr-pager-btn">Cancel</button><button onclick="submitPinnedNote()" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Pin</button></div>' +
|
|
31
|
+
'<div style="display:flex;justify-content:flex-end;gap:8px"><button onclick="closeModal()" class="pr-pager-btn">Cancel</button><button onclick="submitPinnedNote(event)" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Pin</button></div>' +
|
|
32
32
|
'</div>';
|
|
33
33
|
document.getElementById('modal').classList.add('open');
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
async function submitPinnedNote() {
|
|
37
|
-
var btn = event?.target; if (btn) { btn.disabled = true; btn.textContent = 'Pinning...'; }
|
|
36
|
+
async function submitPinnedNote(e) {
|
|
37
|
+
var btn = (e || window.event)?.target; if (btn) { btn.disabled = true; btn.textContent = 'Pinning...'; }
|
|
38
38
|
const title = document.getElementById('pin-title').value;
|
|
39
39
|
const content = document.getElementById('pin-content').value;
|
|
40
40
|
const level = document.getElementById('pin-level').value;
|
|
@@ -50,7 +50,7 @@ async function submitPinnedNote() {
|
|
|
50
50
|
async function removePinnedNote(title) {
|
|
51
51
|
if (!confirm('Unpin "' + title + '"?')) return;
|
|
52
52
|
markDeleted('pin:' + title);
|
|
53
|
-
const btn = event?.target; if (btn) { const card = btn.closest('.pinned-card') || btn.parentElement?.parentElement; if (card) card.remove(); }
|
|
53
|
+
const btn = (window.event)?.target; if (btn) { const card = btn.closest('.pinned-card') || btn.parentElement?.parentElement; if (card) card.remove(); }
|
|
54
54
|
try {
|
|
55
55
|
const res = await fetch('/api/pinned/remove', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title }) });
|
|
56
56
|
if (!res.ok) { alert('Unpin failed'); refresh(); }
|
|
@@ -200,7 +200,7 @@ function renderPrdProgress(prog) {
|
|
|
200
200
|
|
|
201
201
|
const renderGroupHeader = (g) => {
|
|
202
202
|
const done = g.items.filter(i => i.status === 'done').length;
|
|
203
|
-
const wip = g.items.filter(i => i.status === 'dispatched' || i.status === '
|
|
203
|
+
const wip = g.items.filter(i => i.status === 'dispatched' || i.status === 'pending').length;
|
|
204
204
|
const summary = (g.summary || '').replace(/^Convert plan to PRD:\s*/i, '').slice(0, 80);
|
|
205
205
|
const isAwaitingApproval = g.planStatus === 'awaiting-approval';
|
|
206
206
|
const isPaused = g.planStatus === 'paused';
|
|
@@ -564,7 +564,7 @@ async function prdItemEdit(source, itemId) {
|
|
|
564
564
|
let completionHtml = '';
|
|
565
565
|
const isDone = item.status === 'done';
|
|
566
566
|
const isFailed = item.status === 'failed';
|
|
567
|
-
const isActive = item.status === 'dispatched' || item.status === '
|
|
567
|
+
const isActive = item.status === 'dispatched' || item.status === 'pending';
|
|
568
568
|
|
|
569
569
|
if (isDone || isFailed || isActive) {
|
|
570
570
|
const agent = wi?.dispatched_to || completedEntry?.agent || '';
|
|
@@ -604,9 +604,9 @@ async function prdItemEdit(source, itemId) {
|
|
|
604
604
|
'</select></div>' +
|
|
605
605
|
'<div><label style="font-size:11px;color:var(--muted);display:block;margin-bottom:4px">Complexity</label>' +
|
|
606
606
|
'<select id="prd-edit-complexity" style="padding:4px 8px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text)">' +
|
|
607
|
-
'<option value="small"' + (item.complexity === 'small' ? ' selected' : '') + '>Small</option>' +
|
|
608
|
-
'<option value="medium"' + (item.complexity === 'medium' ? ' selected' : '') + '>Medium</option>' +
|
|
609
|
-
'<option value="large"' + (item.complexity === 'large' ? ' selected' : '') + '>Large</option>' +
|
|
607
|
+
'<option value="small"' + ((item.estimated_complexity || item.complexity) === 'small' ? ' selected' : '') + '>Small</option>' +
|
|
608
|
+
'<option value="medium"' + ((item.estimated_complexity || item.complexity) === 'medium' ? ' selected' : '') + '>Medium</option>' +
|
|
609
|
+
'<option value="large"' + ((item.estimated_complexity || item.complexity) === 'large' ? ' selected' : '') + '>Large</option>' +
|
|
610
610
|
'</select></div>' +
|
|
611
611
|
'</div>' +
|
|
612
612
|
'<div style="display:flex;gap:8px">' +
|
|
@@ -20,7 +20,7 @@ function prRow(pr) {
|
|
|
20
20
|
const prId = pr.id || '—';
|
|
21
21
|
return '<tr>' +
|
|
22
22
|
'<td><span class="pr-id">' + escHtml(String(prId)) + '</span></td>' +
|
|
23
|
-
'<td><a class="pr-title" href="' + escHtml(url) + '" target="_blank">' + escHtml(pr.title || 'Untitled') + '</a></td>' +
|
|
23
|
+
'<td><a class="pr-title" href="' + escHtml(safeUrl(url)) + '" target="_blank" rel="noopener">' + escHtml(pr.title || 'Untitled') + '</a></td>' +
|
|
24
24
|
'<td><span class="pr-agent">' + escHtml(pr.agent || '—') + '</span></td>' +
|
|
25
25
|
'<td><span class="pr-branch">' + escHtml(pr.branch || '—') + '</span></td>' +
|
|
26
26
|
'<td><span class="pr-badge ' + reviewClass + '">' + escHtml(reviewLabel) + '</span></td>' +
|
|
@@ -33,7 +33,7 @@ function prRow(pr) {
|
|
|
33
33
|
|
|
34
34
|
function prTableHtml(rows) {
|
|
35
35
|
return '<div class="pr-table-wrap"><table class="pr-table"><thead><tr>' +
|
|
36
|
-
'<th>PR</th><th>Title</th><th>Agent</th><th>Branch</th><th>Review</th><th>Signed Off By</th><th>Build</th><th>Status</th><th>Created</th
|
|
36
|
+
'<th>PR</th><th>Title</th><th>Agent</th><th>Branch</th><th>Review</th><th>Signed Off By</th><th>Build</th><th>Status</th><th>Created</th>' +
|
|
37
37
|
'</tr></thead><tbody>' + rows + '</tbody></table></div>';
|
|
38
38
|
}
|
|
39
39
|
|
package/dashboard/js/state.js
CHANGED
|
@@ -89,6 +89,7 @@ function prunePrdRequeueState(workItems) {
|
|
|
89
89
|
|
|
90
90
|
function rerenderPrdFromCache() {
|
|
91
91
|
if (!window._lastStatus || !window._lastStatus.prdProgress) return;
|
|
92
|
+
renderPrdProgress(window._lastStatus.prdProgress);
|
|
92
93
|
renderPrd(window._lastStatus.prd, window._lastStatus.prdProgress);
|
|
93
94
|
}
|
|
94
95
|
|
package/dashboard/js/utils.js
CHANGED
|
@@ -12,6 +12,13 @@ function escHtml(s) {
|
|
|
12
12
|
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function safeUrl(url) {
|
|
16
|
+
const s = String(url || '').trim();
|
|
17
|
+
if (/^https?:\/\//i.test(s)) return s;
|
|
18
|
+
if (s === '#' || s === '') return '#';
|
|
19
|
+
return '#'; // block javascript:, data:, etc.
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
function normalizePlanFile(file) {
|
|
16
23
|
return String(file || '').replace(/\\/g, '/').split('/').pop();
|
|
17
24
|
}
|
package/dashboard.js
CHANGED
|
@@ -1898,9 +1898,9 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
1898
1898
|
}
|
|
1899
1899
|
}
|
|
1900
1900
|
|
|
1901
|
-
if (w.status !== '
|
|
1902
|
-
w.status = '
|
|
1903
|
-
|
|
1901
|
+
if (w.status !== 'paused') reset++;
|
|
1902
|
+
w.status = 'paused';
|
|
1903
|
+
w._pausedBy = 'prd-pause';
|
|
1904
1904
|
delete w._resumedAt;
|
|
1905
1905
|
delete w.dispatched_at;
|
|
1906
1906
|
delete w.dispatched_to;
|
|
@@ -3202,8 +3202,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3202
3202
|
const config = queries.getConfig();
|
|
3203
3203
|
const routing = safeRead(path.join(MINIONS_DIR, 'routing.md')) || '';
|
|
3204
3204
|
return jsonReply(res, 200, {
|
|
3205
|
-
engine: config.engine || {},
|
|
3206
|
-
claude: config.claude || {},
|
|
3205
|
+
engine: { ...shared.ENGINE_DEFAULTS, ...(config.engine || {}) },
|
|
3206
|
+
claude: { ...shared.DEFAULT_CLAUDE, ...(config.claude || {}) },
|
|
3207
3207
|
agents: config.agents || {},
|
|
3208
3208
|
routing,
|
|
3209
3209
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.365",
|
|
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"
|