@yemi33/minions 0.1.73 → 0.1.75

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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.75 (2026-03-31)
4
+
5
+ ### Dashboard
6
+ - dashboard.js
7
+
8
+ ## 0.1.74 (2026-03-31)
9
+
10
+ ### Dashboard
11
+ - dashboard/js/render-meetings.js
12
+
3
13
  ## 0.1.73 (2026-03-31)
4
14
 
5
15
  ### Dashboard
@@ -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 statusColors = { investigating: 'var(--blue)', debating: 'var(--purple,#a855f7)', concluding: 'var(--yellow)', completed: 'var(--green)' };
14
- const statusLabels = { investigating: 'Round 1 — Investigating', debating: 'Round 2 — Debating', concluding: 'Round 3 — Concluding', completed: 'Completed' };
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 = meetings.map(m => {
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
- '<span style="color:' + statusColor + ';font-size:11px;font-weight:600">' + statusLabel + '</span>' +
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?.slice(0, 500) || '') + (m.findings[agent].content?.length > 500 ? '...' : '') + '</div></div>';
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?.slice(0, 500) || '') + (m.debate[agent].content?.length > 500 ? '...' : '') + '</div></div>';
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?.slice(0, 1000) || '') + '</div></div>';
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 !== 'completed') {
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 CHANGED
@@ -600,17 +600,19 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
600
600
  async function ccDocCall({ message, document, title, filePath, selection, canEdit, isJson }) {
601
601
  const docContext = `## Document Context\n**${title || 'Document'}**${filePath ? ' (`' + filePath + '`)' : ''}${isJson ? ' (JSON)' : ''}\n${selection ? '\n**Selected text:**\n> ' + selection.slice(0, 1500) + '\n' : ''}\n\`\`\`\n${document.slice(0, 20000)}\n\`\`\`\n${canEdit ? '\nIf editing: respond with your explanation, then `---DOCUMENT---` on its own line, then the COMPLETE updated file.' : '\n(Read-only — answer questions only.)'}`;
602
602
 
603
- // Plans: Sonnet with tools for codebase-aware Q&A and edits
603
+ // Plans + meetings: Sonnet with tools for multi-turn codebase-aware Q&A
604
604
  // Everything else: Haiku, 1 turn, no tools — fast
605
605
  const isPlan = filePath && /^plans\//.test(filePath);
606
+ const isMeeting = filePath && /^meetings\//.test(filePath);
607
+ const isRich = isPlan || isMeeting;
606
608
  const result = await ccCall(message, {
607
609
  store: 'doc', sessionKey: filePath || title,
608
610
  extraContext: docContext, label: 'doc-chat',
609
- timeout: isPlan ? 300000 : 60000,
610
- maxTurns: isPlan ? 10 : 1,
611
- model: isPlan ? 'sonnet' : 'haiku',
612
- allowedTools: isPlan ? 'Read,Glob,Grep' : '',
613
- skipStatePreamble: !isPlan,
611
+ timeout: isRich ? 300000 : 60000,
612
+ maxTurns: isRich ? 10 : 1,
613
+ model: isRich ? 'sonnet' : 'haiku',
614
+ allowedTools: isRich ? 'Read,Glob,Grep' : '',
615
+ skipStatePreamble: !isRich,
614
616
  });
615
617
 
616
618
  if (result.code !== 0 || !result.text) {
@@ -3155,6 +3157,32 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3155
3157
  invalidateStatusCache();
3156
3158
  return jsonReply(res, 200, { ok: true });
3157
3159
  }},
3160
+ { method: 'POST', path: '/api/meetings/archive', desc: 'Archive a meeting', params: 'id', handler: async (req, res) => {
3161
+ const body = await readBody(req);
3162
+ if (!body.id) return jsonReply(res, 400, { error: 'id required' });
3163
+ const { archiveMeeting } = require('./engine/meeting');
3164
+ const meeting = archiveMeeting(body.id);
3165
+ if (!meeting) return jsonReply(res, 404, { error: 'Meeting not found' });
3166
+ invalidateStatusCache();
3167
+ return jsonReply(res, 200, { ok: true });
3168
+ }},
3169
+ { method: 'POST', path: '/api/meetings/unarchive', desc: 'Unarchive a meeting', params: 'id', handler: async (req, res) => {
3170
+ const body = await readBody(req);
3171
+ if (!body.id) return jsonReply(res, 400, { error: 'id required' });
3172
+ const { unarchiveMeeting } = require('./engine/meeting');
3173
+ const meeting = unarchiveMeeting(body.id);
3174
+ if (!meeting) return jsonReply(res, 404, { error: 'Meeting not found or not archived' });
3175
+ invalidateStatusCache();
3176
+ return jsonReply(res, 200, { ok: true });
3177
+ }},
3178
+ { method: 'POST', path: '/api/meetings/delete', desc: 'Delete a meeting', params: 'id', handler: async (req, res) => {
3179
+ const body = await readBody(req);
3180
+ if (!body.id) return jsonReply(res, 400, { error: 'id required' });
3181
+ const { deleteMeeting } = require('./engine/meeting');
3182
+ if (!deleteMeeting(body.id)) return jsonReply(res, 404, { error: 'Meeting not found' });
3183
+ invalidateStatusCache();
3184
+ return jsonReply(res, 200, { ok: true });
3185
+ }},
3158
3186
 
3159
3187
  // Engine
3160
3188
  { method: 'POST', path: '/api/engine/wakeup', desc: 'Trigger immediate engine tick via control.json signal', handler: async (req, res) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.73",
3
+ "version": "0.1.75",
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"