@yemi33/minions 0.1.72 → 0.1.74
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 +12 -0
- package/dashboard/js/refresh.js +35 -0
- package/dashboard/js/render-meetings.js +99 -9
- package/dashboard/js/state.js +5 -1
- package/dashboard/styles.css +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.74 (2026-03-31)
|
|
4
|
+
|
|
5
|
+
### Dashboard
|
|
6
|
+
- dashboard/js/render-meetings.js
|
|
7
|
+
|
|
8
|
+
## 0.1.73 (2026-03-31)
|
|
9
|
+
|
|
10
|
+
### Dashboard
|
|
11
|
+
- dashboard/js/refresh.js
|
|
12
|
+
- dashboard/js/state.js
|
|
13
|
+
- dashboard/styles.css
|
|
14
|
+
|
|
3
15
|
## 0.1.72 (2026-03-31)
|
|
4
16
|
|
|
5
17
|
### Dashboard
|
package/dashboard/js/refresh.js
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
// refresh.js — Main refresh loop and initialization extracted from dashboard.html
|
|
2
2
|
|
|
3
|
+
// Sidebar activity indicators — detect changes between refreshes
|
|
4
|
+
let _prevCounts = {};
|
|
5
|
+
function _detectPageChanges(data) {
|
|
6
|
+
const counts = {
|
|
7
|
+
completions: (data.dispatch?.completed || []).length,
|
|
8
|
+
workDone: (data.workItems || []).filter(w => w.status === 'done' || w.status === 'failed').length,
|
|
9
|
+
workTotal: (data.workItems || []).length,
|
|
10
|
+
prdComplete: data.prdProgress?.complete || 0,
|
|
11
|
+
prsMerged: (data.pullRequests || []).filter(p => p.status === 'merged').length,
|
|
12
|
+
inbox: (data.inbox?.items || []).length,
|
|
13
|
+
meetingRounds: (data.meetings || []).reduce((s, m) => s + (m.round || 0), 0),
|
|
14
|
+
};
|
|
15
|
+
const changes = {};
|
|
16
|
+
if (_prevCounts.completions !== undefined) {
|
|
17
|
+
if (counts.completions > _prevCounts.completions) changes.home = true;
|
|
18
|
+
if (counts.workDone > _prevCounts.workDone || counts.workTotal > _prevCounts.workTotal) changes.work = true;
|
|
19
|
+
if (counts.prdComplete > _prevCounts.prdComplete) changes.plans = true;
|
|
20
|
+
if (counts.prsMerged > _prevCounts.prsMerged) changes.prs = true;
|
|
21
|
+
if (counts.inbox > _prevCounts.inbox) changes.inbox = true;
|
|
22
|
+
if (counts.meetingRounds > _prevCounts.meetingRounds) changes.meetings = true;
|
|
23
|
+
}
|
|
24
|
+
_prevCounts = counts;
|
|
25
|
+
return changes;
|
|
26
|
+
}
|
|
27
|
+
|
|
3
28
|
function _processStatusUpdate(data) {
|
|
4
29
|
// Detect fresh install — clear stale browser state if install ID changed
|
|
5
30
|
if (data.installId) {
|
|
@@ -46,6 +71,16 @@ function _processStatusUpdate(data) {
|
|
|
46
71
|
// Refresh KB and plans less frequently (every 3rd cycle = ~12s)
|
|
47
72
|
if (!window._kbRefreshCount) window._kbRefreshCount = 0;
|
|
48
73
|
if (window._kbRefreshCount++ % 3 === 0) { refreshKnowledgeBase(); refreshPlans(); }
|
|
74
|
+
|
|
75
|
+
// Sidebar activity indicators — show red dot on pages with new activity
|
|
76
|
+
try {
|
|
77
|
+
const changes = _detectPageChanges(data);
|
|
78
|
+
for (const page of Object.keys(changes)) {
|
|
79
|
+
if (currentPage === page) continue; // don't badge the active page
|
|
80
|
+
const link = document.querySelector('.sidebar-link[data-page="' + page + '"]');
|
|
81
|
+
if (link && !link.querySelector('.notif-badge')) showNotifBadge(link, 'done');
|
|
82
|
+
}
|
|
83
|
+
} catch { /* expected on first load */ }
|
|
49
84
|
}
|
|
50
85
|
|
|
51
86
|
async function refresh() {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// render-meetings.js — Team meeting rendering
|
|
2
2
|
|
|
3
|
+
let _showArchived = false;
|
|
4
|
+
|
|
3
5
|
function renderMeetings(meetings) {
|
|
4
6
|
const el = document.getElementById('meetings-content');
|
|
5
7
|
const countEl = document.getElementById('meetings-count');
|
|
@@ -8,12 +10,22 @@ function renderMeetings(meetings) {
|
|
|
8
10
|
el.innerHTML = '<p class="empty">No meetings yet. Start one to have agents investigate, debate, and conclude on a topic.</p>';
|
|
9
11
|
return;
|
|
10
12
|
}
|
|
11
|
-
countEl.textContent = meetings.length;
|
|
12
13
|
|
|
13
|
-
const
|
|
14
|
-
const
|
|
14
|
+
const active = meetings.filter(m => m.status !== 'archived');
|
|
15
|
+
const archived = meetings.filter(m => m.status === 'archived');
|
|
16
|
+
countEl.textContent = active.length;
|
|
17
|
+
|
|
18
|
+
const statusColors = { investigating: 'var(--blue)', debating: 'var(--purple,#a855f7)', concluding: 'var(--yellow)', completed: 'var(--green)', archived: 'var(--muted)' };
|
|
19
|
+
const statusLabels = { investigating: 'Round 1 — Investigating', debating: 'Round 2 — Debating', concluding: 'Round 3 — Concluding', completed: 'Completed', archived: 'Archived' };
|
|
20
|
+
|
|
21
|
+
const visible = _showArchived ? meetings : active;
|
|
22
|
+
if (visible.length === 0) {
|
|
23
|
+
el.innerHTML = '<p class="empty">No active meetings.</p>';
|
|
24
|
+
if (archived.length) el.innerHTML += '<div style="text-align:center;margin-top:8px"><button class="pr-pager-btn" style="font-size:10px" onclick="_toggleArchivedMeetings()">Show ' + archived.length + ' archived</button></div>';
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
15
27
|
|
|
16
|
-
el.innerHTML =
|
|
28
|
+
el.innerHTML = visible.map(m => {
|
|
17
29
|
const statusColor = statusColors[m.status] || 'var(--muted)';
|
|
18
30
|
const statusLabel = statusLabels[m.status] || m.status;
|
|
19
31
|
const participantBadges = (m.participants || []).map(p => {
|
|
@@ -23,15 +35,35 @@ function renderMeetings(meetings) {
|
|
|
23
35
|
return '<span style="background:var(--surface2);border:1px solid var(--border);border-radius:4px;padding:2px 6px;font-size:10px">' + icon + ' ' + escHtml(p) + '</span>';
|
|
24
36
|
}).join(' ');
|
|
25
37
|
|
|
38
|
+
const dt = m.completedAt || m.createdAt;
|
|
39
|
+
const timeStr = dt ? new Date(dt).toLocaleString(undefined, { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }) : '';
|
|
40
|
+
|
|
26
41
|
return '<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:12px 16px;margin-bottom:8px;cursor:pointer" onclick="openMeetingDetail(\'' + escHtml(m.id) + '\')">' +
|
|
27
42
|
'<div style="display:flex;justify-content:space-between;align-items:center">' +
|
|
28
43
|
'<strong style="font-size:13px">' + escHtml(m.title) + '</strong>' +
|
|
29
|
-
'<
|
|
44
|
+
'<div style="display:flex;align-items:center;gap:8px">' +
|
|
45
|
+
'<span style="color:' + statusColor + ';font-size:11px;font-weight:600">' + statusLabel + '</span>' +
|
|
46
|
+
(m.status === 'archived'
|
|
47
|
+
? '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px" onclick="event.stopPropagation();_unarchiveMeeting(\'' + escHtml(m.id) + '\')">Unarchive</button>'
|
|
48
|
+
: (m.status === 'completed' ? '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px" onclick="event.stopPropagation();_archiveMeeting(\'' + escHtml(m.id) + '\')">Archive</button>' : '')) +
|
|
49
|
+
'<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--red);border-color:var(--red)" onclick="event.stopPropagation();_deleteMeeting(\'' + escHtml(m.id) + '\')">Delete</button>' +
|
|
50
|
+
'</div>' +
|
|
30
51
|
'</div>' +
|
|
52
|
+
(timeStr ? '<div style="margin-top:4px;font-size:10px;color:var(--muted)">' + escHtml(timeStr) + '</div>' : '') +
|
|
31
53
|
'<div style="margin-top:6px;display:flex;gap:6px;flex-wrap:wrap">' + participantBadges + '</div>' +
|
|
32
54
|
'<div style="margin-top:6px;font-size:11px;color:var(--muted)">' + escHtml((m.agenda || '').slice(0, 100)) + (m.agenda?.length > 100 ? '...' : '') + '</div>' +
|
|
33
55
|
'</div>';
|
|
34
56
|
}).join('');
|
|
57
|
+
|
|
58
|
+
if (archived.length > 0) {
|
|
59
|
+
el.innerHTML += '<div style="text-align:center;margin-top:8px"><button class="pr-pager-btn" style="font-size:10px" onclick="_toggleArchivedMeetings()">' +
|
|
60
|
+
(_showArchived ? 'Hide' : 'Show') + ' ' + archived.length + ' archived</button></div>';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function _toggleArchivedMeetings() {
|
|
65
|
+
_showArchived = !_showArchived;
|
|
66
|
+
refresh();
|
|
35
67
|
}
|
|
36
68
|
|
|
37
69
|
function openMeetingDetail(id) {
|
|
@@ -64,14 +96,14 @@ function openMeetingDetail(id) {
|
|
|
64
96
|
if (m.findings?.[agent]) {
|
|
65
97
|
html += '<div style="padding:8px 12px;font-size:11px;border-bottom:1px solid var(--border)">' +
|
|
66
98
|
'<div style="color:var(--muted);font-size:10px;margin-bottom:4px">Round 1 — Findings</div>' +
|
|
67
|
-
'<div style="white-space:pre-wrap;word-break:break-word">' + escHtml(m.findings[agent].content
|
|
99
|
+
'<div style="white-space:pre-wrap;word-break:break-word;max-height:300px;overflow-y:auto">' + escHtml(m.findings[agent].content || '') + '</div></div>';
|
|
68
100
|
}
|
|
69
101
|
|
|
70
102
|
// Debate
|
|
71
103
|
if (m.debate?.[agent]) {
|
|
72
104
|
html += '<div style="padding:8px 12px;font-size:11px;border-bottom:1px solid var(--border)">' +
|
|
73
105
|
'<div style="color:var(--muted);font-size:10px;margin-bottom:4px">Round 2 — Debate</div>' +
|
|
74
|
-
'<div style="white-space:pre-wrap;word-break:break-word">' + escHtml(m.debate[agent].content
|
|
106
|
+
'<div style="white-space:pre-wrap;word-break:break-word;max-height:300px;overflow-y:auto">' + escHtml(m.debate[agent].content || '') + '</div></div>';
|
|
75
107
|
}
|
|
76
108
|
|
|
77
109
|
// Status
|
|
@@ -86,7 +118,7 @@ function openMeetingDetail(id) {
|
|
|
86
118
|
if (m.conclusion) {
|
|
87
119
|
html += '<div style="background:rgba(63,185,80,0.08);border:1px solid var(--green);border-radius:6px;padding:10px 14px">' +
|
|
88
120
|
'<div style="color:var(--green);font-weight:600;font-size:12px;margin-bottom:6px">Conclusion (by ' + escHtml(m.conclusion.agent || '?') + ')</div>' +
|
|
89
|
-
'<div style="font-size:12px;white-space:pre-wrap;word-break:break-word">' + escHtml(m.conclusion.content
|
|
121
|
+
'<div style="font-size:12px;white-space:pre-wrap;word-break:break-word;max-height:400px;overflow-y:auto">' + escHtml(m.conclusion.content || '') + '</div></div>';
|
|
90
122
|
}
|
|
91
123
|
|
|
92
124
|
// Human notes
|
|
@@ -98,7 +130,17 @@ function openMeetingDetail(id) {
|
|
|
98
130
|
}
|
|
99
131
|
|
|
100
132
|
// Actions
|
|
101
|
-
if (m.status
|
|
133
|
+
if (m.status === 'archived') {
|
|
134
|
+
html += '<div style="display:flex;gap:8px;border-top:1px solid var(--border);padding-top:8px">' +
|
|
135
|
+
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" onclick="_unarchiveMeeting(\'' + escHtml(m.id) + '\')">Unarchive</button>' +
|
|
136
|
+
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--red);border-color:var(--red)" onclick="_deleteMeeting(\'' + escHtml(m.id) + '\')">Delete</button>' +
|
|
137
|
+
'</div>';
|
|
138
|
+
} else if (m.status === 'completed') {
|
|
139
|
+
html += '<div style="display:flex;gap:8px;border-top:1px solid var(--border);padding-top:8px">' +
|
|
140
|
+
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px" onclick="_archiveMeeting(\'' + escHtml(m.id) + '\')">Archive</button>' +
|
|
141
|
+
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--red);border-color:var(--red)" onclick="_deleteMeeting(\'' + escHtml(m.id) + '\')">Delete</button>' +
|
|
142
|
+
'</div>';
|
|
143
|
+
} else {
|
|
102
144
|
html += '<div style="display:flex;gap:8px;border-top:1px solid var(--border);padding-top:8px">' +
|
|
103
145
|
'<input id="meeting-note-input" type="text" placeholder="Add context for all agents..." style="flex:1;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:12px" onkeydown="if(event.key===\'Enter\')_submitMeetingNote(\'' + escHtml(m.id) + '\')">' +
|
|
104
146
|
'<button onclick="_submitMeetingNote(\'' + escHtml(m.id) + '\')" style="padding:6px 12px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer;font-size:11px">Add Note</button>' +
|
|
@@ -106,6 +148,7 @@ function openMeetingDetail(id) {
|
|
|
106
148
|
'<div style="display:flex;gap:8px;margin-top:4px">' +
|
|
107
149
|
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--yellow);border-color:var(--yellow)" onclick="_advanceMeeting(\'' + escHtml(m.id) + '\')">Skip to Next Round</button>' +
|
|
108
150
|
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--red);border-color:var(--red)" onclick="_endMeeting(\'' + escHtml(m.id) + '\')">End Meeting</button>' +
|
|
151
|
+
'<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--red);border-color:var(--red)" onclick="_deleteMeeting(\'' + escHtml(m.id) + '\')">Delete</button>' +
|
|
109
152
|
'</div>';
|
|
110
153
|
}
|
|
111
154
|
|
|
@@ -115,6 +158,16 @@ function openMeetingDetail(id) {
|
|
|
115
158
|
document.getElementById('modal-body').innerHTML = html;
|
|
116
159
|
document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
117
160
|
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
161
|
+
|
|
162
|
+
// Wire up doc-chat Q&A panel for the meeting transcript
|
|
163
|
+
const transcript = (m.transcript || []).map(t =>
|
|
164
|
+
'### ' + t.agent + ' (' + t.type + ', Round ' + t.round + ')\n\n' + (t.content || '')
|
|
165
|
+
).join('\n\n---\n\n');
|
|
166
|
+
const meetingDoc = '# Meeting: ' + m.title + '\n\n**Agenda:** ' + m.agenda + '\n\n' + transcript;
|
|
167
|
+
_modalDocContext = { title: 'Meeting: ' + m.title, content: meetingDoc, selection: '' };
|
|
168
|
+
_modalFilePath = 'meetings/' + m.id + '.json';
|
|
169
|
+
try { showModalQa(); } catch { /* expected if QA not loaded */ }
|
|
170
|
+
|
|
118
171
|
document.getElementById('modal').classList.add('open');
|
|
119
172
|
})
|
|
120
173
|
.catch(e => alert('Error: ' + e.message));
|
|
@@ -200,4 +253,41 @@ async function _endMeeting(id) {
|
|
|
200
253
|
} catch (e) { alert('Error: ' + e.message); }
|
|
201
254
|
}
|
|
202
255
|
|
|
256
|
+
async function _archiveMeeting(id) {
|
|
257
|
+
try {
|
|
258
|
+
const res = await fetch('/api/meetings/archive', {
|
|
259
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
260
|
+
body: JSON.stringify({ id })
|
|
261
|
+
});
|
|
262
|
+
if (!res.ok) { const d = await res.json().catch(() => ({})); alert('Failed: ' + (d.error || 'unknown')); return; }
|
|
263
|
+
try { closeModal(); } catch { /* may not be open */ }
|
|
264
|
+
refresh();
|
|
265
|
+
} catch (e) { alert('Error: ' + e.message); }
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function _unarchiveMeeting(id) {
|
|
269
|
+
try {
|
|
270
|
+
const res = await fetch('/api/meetings/unarchive', {
|
|
271
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
272
|
+
body: JSON.stringify({ id })
|
|
273
|
+
});
|
|
274
|
+
if (!res.ok) { const d = await res.json().catch(() => ({})); alert('Failed: ' + (d.error || 'unknown')); return; }
|
|
275
|
+
try { closeModal(); } catch { /* may not be open */ }
|
|
276
|
+
refresh();
|
|
277
|
+
} catch (e) { alert('Error: ' + e.message); }
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function _deleteMeeting(id) {
|
|
281
|
+
if (!confirm('Delete this meeting? This cannot be undone.')) return;
|
|
282
|
+
try {
|
|
283
|
+
const res = await fetch('/api/meetings/delete', {
|
|
284
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
285
|
+
body: JSON.stringify({ id })
|
|
286
|
+
});
|
|
287
|
+
if (!res.ok) { const d = await res.json().catch(() => ({})); alert('Failed: ' + (d.error || 'unknown')); return; }
|
|
288
|
+
try { closeModal(); } catch { /* may not be open */ }
|
|
289
|
+
refresh();
|
|
290
|
+
} catch (e) { alert('Error: ' + e.message); }
|
|
291
|
+
}
|
|
292
|
+
|
|
203
293
|
window.MinionsMeetings = { renderMeetings, openMeetingDetail, openCreateMeetingModal };
|
package/dashboard/js/state.js
CHANGED
|
@@ -21,7 +21,11 @@ function switchPage(page, pushState) {
|
|
|
21
21
|
if (target) target.classList.add('active');
|
|
22
22
|
document.querySelectorAll('.sidebar-link').forEach(l => l.classList.remove('active'));
|
|
23
23
|
const link = document.querySelector('.sidebar-link[data-page="' + page + '"]');
|
|
24
|
-
if (link)
|
|
24
|
+
if (link) {
|
|
25
|
+
link.classList.add('active');
|
|
26
|
+
// Clear notification badge when visiting a page
|
|
27
|
+
try { clearNotifBadge(link); } catch { /* clearNotifBadge may not be loaded yet */ }
|
|
28
|
+
}
|
|
25
29
|
if (pushState !== false) {
|
|
26
30
|
const url = page === 'home' ? '/' : '/' + page;
|
|
27
31
|
history.pushState({ page }, '', url);
|
package/dashboard/styles.css
CHANGED
|
@@ -43,7 +43,8 @@
|
|
|
43
43
|
/* Sidebar navigation */
|
|
44
44
|
.page-layout { display: flex; height: calc(100vh - 44px); overflow: hidden; }
|
|
45
45
|
.sidebar { width: 150px; min-width: 150px; background: var(--surface); border-right: 1px solid var(--border); padding: var(--space-4) 0; overflow-y: auto; position: sticky; top: 0; }
|
|
46
|
-
.sidebar-link { display: flex; align-items: center; justify-content: space-between; padding: 8px 14px; color: var(--muted); text-decoration: none; font-size: var(--text-sm); border-left: 3px solid transparent; transition: all var(--transition-fast); cursor: pointer; }
|
|
46
|
+
.sidebar-link { display: flex; align-items: center; justify-content: space-between; padding: 8px 14px; color: var(--muted); text-decoration: none; font-size: var(--text-sm); border-left: 3px solid transparent; transition: all var(--transition-fast); cursor: pointer; position: relative; }
|
|
47
|
+
.sidebar-link .notif-badge.done { top: 50%; right: 6px; transform: translateY(-50%); width: 6px; height: 6px; }
|
|
47
48
|
.sidebar-link:hover { color: var(--text); background: var(--surface2); }
|
|
48
49
|
.sidebar-link.active { color: var(--blue); border-left-color: var(--blue); background: var(--surface2); font-weight: 600; }
|
|
49
50
|
.sidebar-count { font-size: 9px; color: var(--muted); background: var(--surface2); padding: 1px 5px; border-radius: 8px; min-width: 16px; text-align: center; }
|
package/package.json
CHANGED