@yemi33/minions 0.1.122 → 0.1.124

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,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.124 (2026-04-01)
4
+
5
+ ### Dashboard
6
+ - dashboard/js/render-schedules.js
7
+ - dashboard/styles.css
8
+
9
+ ## 0.1.123 (2026-04-01)
10
+
11
+ ### Engine
12
+ - engine/ado-mcp-wrapper.js
13
+
14
+ ### Dashboard
15
+ - dashboard.js
16
+ - dashboard/js/detail-panel.js
17
+ - dashboard/js/modal-qa.js
18
+ - dashboard/js/render-inbox.js
19
+ - dashboard/js/render-kb.js
20
+ - dashboard/js/render-plans.js
21
+ - dashboard/js/utils.js
22
+ - dashboard/styles.css
23
+
3
24
  ## 0.1.122 (2026-04-01)
4
25
 
5
26
  ### Other
@@ -79,7 +79,16 @@ function renderDetailContent(detail, tab) {
79
79
  '</div>';
80
80
  startLiveStream(currentAgentId);
81
81
  } else if (tab === 'charter') {
82
- el.innerHTML = '<div class="section">' + renderMd(detail.charter || 'No charter found.') + '</div>';
82
+ const charterContent = detail.charter || '';
83
+ el.innerHTML =
84
+ '<div style="display:flex;gap:6px;margin-bottom:8px">' +
85
+ '<button class="pr-pager-btn" id="charter-edit-btn" style="font-size:10px;padding:2px 10px" onclick="_toggleCharterEdit()">Edit</button>' +
86
+ '<button class="pr-pager-btn" id="charter-save-btn" style="font-size:10px;padding:2px 10px;color:var(--green);border-color:var(--green);display:none" onclick="_saveCharter()">Save</button>' +
87
+ '<button class="pr-pager-btn" id="charter-cancel-btn" style="font-size:10px;padding:2px 10px;display:none" onclick="_cancelCharterEdit()">Cancel</button>' +
88
+ '</div>' +
89
+ '<div id="charter-view" class="section">' + renderMd(charterContent || 'No charter found. Click Edit to create one.') + '</div>' +
90
+ '<textarea id="charter-editor" style="display:none;width:100%;min-height:300px;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-family:Consolas,monospace;font-size:12px;resize:vertical">' + escHtml(charterContent) + '</textarea>';
91
+ el._charterRaw = charterContent;
83
92
  } else if (tab === 'history') {
84
93
  let html = '';
85
94
  // Recent dispatch results
@@ -99,11 +108,52 @@ function renderDetailContent(detail, tab) {
99
108
  html += '</tbody></table>';
100
109
  }
101
110
  // Raw history.md
102
- html += '<h4>Task History</h4><div class="section">' + escHtml(detail.history || 'No history yet.') + '</div>';
111
+ html += '<h4>Task History</h4><div class="section">' + renderMd(detail.history || 'No history yet.') + '</div>';
103
112
  el.innerHTML = html;
104
113
  } else if (tab === 'output') {
105
- el.innerHTML = '<div class="section">' + escHtml(detail.outputLog || 'No output log. The coordinator will save agent output here when tasks complete.') + '</div>';
114
+ el.innerHTML = '<div class="section">' + renderMd(detail.outputLog || 'No output log. The coordinator will save agent output here when tasks complete.') + '</div>';
106
115
  }
107
116
  }
108
117
 
118
+ function _toggleCharterEdit() {
119
+ document.getElementById('charter-view').style.display = 'none';
120
+ document.getElementById('charter-editor').style.display = '';
121
+ document.getElementById('charter-edit-btn').style.display = 'none';
122
+ document.getElementById('charter-save-btn').style.display = '';
123
+ document.getElementById('charter-cancel-btn').style.display = '';
124
+ document.getElementById('charter-editor').focus();
125
+ }
126
+
127
+ function _cancelCharterEdit() {
128
+ const el = document.getElementById('detail-content');
129
+ document.getElementById('charter-editor').value = el._charterRaw || '';
130
+ document.getElementById('charter-view').style.display = '';
131
+ document.getElementById('charter-editor').style.display = 'none';
132
+ document.getElementById('charter-edit-btn').style.display = '';
133
+ document.getElementById('charter-save-btn').style.display = 'none';
134
+ document.getElementById('charter-cancel-btn').style.display = 'none';
135
+ }
136
+
137
+ async function _saveCharter() {
138
+ const content = document.getElementById('charter-editor').value;
139
+ const btn = document.getElementById('charter-save-btn');
140
+ btn.textContent = 'Saving...'; btn.style.pointerEvents = 'none';
141
+ try {
142
+ const res = await fetch('/api/agents/charter', {
143
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
144
+ body: JSON.stringify({ agent: currentAgentId, content })
145
+ });
146
+ if (res.ok) {
147
+ document.getElementById('charter-view').innerHTML = renderMd(content);
148
+ document.getElementById('detail-content')._charterRaw = content;
149
+ _cancelCharterEdit();
150
+ showToast('cmd-toast', 'Charter saved', true);
151
+ } else {
152
+ const d = await res.json().catch(() => ({}));
153
+ alert('Save failed: ' + (d.error || 'unknown'));
154
+ }
155
+ } catch (e) { alert('Save failed: ' + e.message); }
156
+ btn.textContent = 'Save'; btn.style.pointerEvents = '';
157
+ }
158
+
109
159
  window.MinionsDetail = { closeDetail, renderDetailTabs, switchTab, renderDetailContent };
@@ -220,7 +220,7 @@ async function _processQaMessage(message, selection) {
220
220
  if (isJson) {
221
221
  body.textContent = display;
222
222
  } else {
223
- body.innerHTML = '<div style="font-size:12px;line-height:1.6">' + renderMd(display) + '</div>';
223
+ body.innerHTML = renderMd(display);
224
224
  body.style.fontFamily = "'Segoe UI', system-ui, sans-serif";
225
225
  body.style.whiteSpace = 'normal';
226
226
  }
@@ -65,7 +65,7 @@ function openNotesModal() {
65
65
  if (!preview) return;
66
66
  const content = preview._rawContent || preview.textContent;
67
67
  document.getElementById('modal-title').textContent = 'Team Notes';
68
- document.getElementById('modal-body').innerHTML = '<div style="font-size:12px;line-height:1.6">' + renderMd(content) + '</div>';
68
+ document.getElementById('modal-body').innerHTML = renderMd(content);
69
69
  document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
70
70
  document.getElementById('modal-body').style.whiteSpace = 'normal';
71
71
  _modalDocContext = { title: 'Team Notes', content, selection: '' };
@@ -164,9 +164,7 @@ async function kbOpenItem(category, file) {
164
164
  const display = content.replace(/^---[\s\S]*?---\n*/m, '');
165
165
  document.getElementById('modal-title').textContent = file;
166
166
  const modalBody = document.getElementById('modal-body');
167
- modalBody.innerHTML = '<div style="font-size:12px;line-height:1.6">' + renderMd(display) + '</div>';
168
- modalBody.style.fontFamily = "'Segoe UI', system-ui, sans-serif";
169
- modalBody.style.whiteSpace = 'normal';
167
+ modalBody.innerHTML = renderMd(display);
170
168
  _modalDocContext = { title: file, content: display, selection: '' };
171
169
  _modalFilePath = 'knowledge/' + category + '/' + file; showModalQa();
172
170
  // Clear notification badge when opening this document
@@ -488,9 +488,7 @@ async function planView(file) {
488
488
  modalBody.style.fontFamily = 'Consolas, monospace';
489
489
  modalBody.style.whiteSpace = 'pre-wrap';
490
490
  } else {
491
- modalBody.innerHTML = '<div style="font-size:12px;line-height:1.6">' + renderMd(text) + '</div>';
492
- modalBody.style.fontFamily = "'Segoe UI', system-ui, sans-serif";
493
- modalBody.style.whiteSpace = 'normal';
491
+ modalBody.innerHTML = renderMd(text);
494
492
  }
495
493
  _modalDocContext = { title, content: text, selection: '' };
496
494
  _modalFilePath = resolvedPath || ((normalizedFile.endsWith('.json') ? 'prd/' : 'plans/') + normalizedFile); showModalQa();
@@ -634,9 +632,7 @@ async function planOpenInDocChat(file) {
634
632
  document.getElementById('modal-body').style.fontFamily = 'Consolas, monospace';
635
633
  document.getElementById('modal-body').style.whiteSpace = 'pre-wrap';
636
634
  } else {
637
- document.getElementById('modal-body').innerHTML = '<div style="font-size:12px;line-height:1.6">' + renderMd(text) + '</div>';
638
- document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
639
- document.getElementById('modal-body').style.whiteSpace = 'normal';
635
+ document.getElementById('modal-body').innerHTML = renderMd(text);
640
636
  }
641
637
  _modalDocContext = { title: title, content: text, selection: '' };
642
638
  _modalFilePath = resolvedPath || ((normalizedFile.endsWith('.json') ? 'prd/' : 'plans/') + normalizedFile); showModalQa();
@@ -669,9 +665,7 @@ async function openVerifyGuide(file) {
669
665
  const content = await fetch('/api/plans/' + encodeURIComponent(normalizedFile)).then(r => r.text());
670
666
  document.getElementById('modal-title').innerHTML = 'Manual Testing Guide' +
671
667
  ' <button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;margin-left:8px;vertical-align:middle" onclick="openArchivedPrdModal()">Back</button>';
672
- document.getElementById('modal-body').innerHTML = '<div style="font-size:12px;line-height:1.6">' + renderMd(content) + '</div>';
673
- document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
674
- document.getElementById('modal-body').style.whiteSpace = 'normal';
668
+ document.getElementById('modal-body').innerHTML = renderMd(content);
675
669
  _modalDocContext = { title: 'Manual Testing Guide', content, selection: '' };
676
670
  _modalFilePath = 'prd/' + normalizedFile; showModalQa();
677
671
  const card = findCardForFile(_modalFilePath);
@@ -179,6 +179,89 @@ async function _parseNaturalCron() {
179
179
 
180
180
  let _schedPage = 0;
181
181
  const SCHED_PER_PAGE = 15;
182
+ let _schedViewMode = 'list';
183
+
184
+ const _SLOT_COLORS = {
185
+ implement: { bg: 'rgba(88,166,255,0.15)', border: 'var(--blue)', text: 'var(--blue)' },
186
+ review: { bg: 'rgba(188,140,255,0.15)', border: 'var(--purple)', text: 'var(--purple)' },
187
+ fix: { bg: 'rgba(210,153,34,0.15)', border: 'var(--yellow)', text: 'var(--yellow)' },
188
+ explore: { bg: 'rgba(139,148,158,0.15)', border: 'var(--muted)', text: 'var(--muted)' },
189
+ test: { bg: 'rgba(227,179,65,0.15)', border: 'var(--orange)', text: 'var(--orange)' },
190
+ ask: { bg: 'rgba(63,185,80,0.15)', border: 'var(--green)', text: 'var(--green)' },
191
+ };
192
+
193
+ function _renderViewToggle() {
194
+ const isList = _schedViewMode === 'list';
195
+ return '<div style="display:flex;gap:4px;margin-bottom:8px">' +
196
+ '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;' + (isList ? 'background:var(--blue);color:#fff;border-color:var(--blue)' : '') + '" onclick="_schedSetView(\'list\')">List</button>' +
197
+ '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;' + (!isList ? 'background:var(--blue);color:#fff;border-color:var(--blue)' : '') + '" onclick="_schedSetView(\'calendar\')">Calendar</button>' +
198
+ '</div>';
199
+ }
200
+
201
+ function _schedSetView(mode) {
202
+ _schedViewMode = mode;
203
+ renderSchedules(window._lastSchedules || []);
204
+ }
205
+
206
+ function _renderScheduleCalendar(schedules) {
207
+ // Parse each schedule into day+hour slots
208
+ const slots = []; // { schedule, day, hour, minute }
209
+ for (const s of schedules) {
210
+ const p = _parseCronToPicker(s.cron || '');
211
+ for (const day of p.days) {
212
+ slots.push({ schedule: s, day: day, hour: p.hour, minute: p.minute });
213
+ }
214
+ }
215
+
216
+ // Find hours that have slots (compact — skip empty hours)
217
+ const hoursUsed = new Set();
218
+ for (const sl of slots) hoursUsed.add(sl.hour);
219
+ const hours = [...hoursUsed].sort(function(a, b) { return a - b; });
220
+
221
+ if (hours.length === 0) {
222
+ return '<p class="empty">No schedules to show in calendar view.</p>';
223
+ }
224
+
225
+ // Build grid: header row + one row per hour
226
+ // Columns: Mon(1) Tue(2) Wed(3) Thu(4) Fri(5) Sat(6) Sun(0)
227
+ var dayOrder = [1, 2, 3, 4, 5, 6, 0];
228
+ var dayLabels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
229
+
230
+ var html = '<div class="sched-cal">';
231
+ // Header row
232
+ html += '<div class="sched-cal-header"></div>'; // empty top-left
233
+ for (var d = 0; d < 7; d++) {
234
+ html += '<div class="sched-cal-header">' + dayLabels[d] + '</div>';
235
+ }
236
+
237
+ // One row per hour
238
+ for (var hi = 0; hi < hours.length; hi++) {
239
+ var hour = hours[hi];
240
+ html += '<div class="sched-cal-hour">' + String(hour).padStart(2, '0') + ':00</div>';
241
+ for (var di = 0; di < 7; di++) {
242
+ var dayNum = dayOrder[di];
243
+ var cellSlots = slots.filter(function(sl) { return sl.hour === hour && sl.day === dayNum; });
244
+ html += '<div class="sched-cal-cell">';
245
+ for (var si = 0; si < cellSlots.length; si++) {
246
+ var sl = cellSlots[si];
247
+ var s = sl.schedule;
248
+ var colors = _SLOT_COLORS[s.type || 'implement'] || _SLOT_COLORS.implement;
249
+ var opacity = s.enabled === false ? '0.4' : '1';
250
+ var strikeStyle = s.enabled === false ? 'text-decoration:line-through;' : '';
251
+ var timeLabel = String(sl.hour).padStart(2, '0') + ':' + String(sl.minute).padStart(2, '0');
252
+ html += '<div class="sched-cal-slot" style="background:' + colors.bg + ';border-left-color:' + colors.border + ';color:' + colors.text + ';opacity:' + opacity + '" ' +
253
+ 'onclick="openScheduleDetail(\'' + escHtml(s.id) + '\')" title="' + escHtml(s.title + ' — ' + timeLabel + ' — ' + (s.type || 'implement')) + '">' +
254
+ '<span style="font-weight:600;' + strikeStyle + '">' + escHtml((s.title || s.id).slice(0, 25)) + '</span>' +
255
+ '<span style="font-size:9px;opacity:0.7"> ' + timeLabel + '</span>' +
256
+ '</div>';
257
+ }
258
+ if (cellSlots.length === 0) html += '&nbsp;';
259
+ html += '</div>';
260
+ }
261
+ }
262
+ html += '</div>';
263
+ return html;
264
+ }
182
265
 
183
266
  function renderSchedules(schedules) {
184
267
  schedules = schedules.filter(function(s) { return !isDeleted('sched:' + s.id); });
@@ -191,45 +274,51 @@ function renderSchedules(schedules) {
191
274
  return;
192
275
  }
193
276
 
194
- const totalPages = Math.ceil(schedules.length / SCHED_PER_PAGE);
195
- if (_schedPage >= totalPages) _schedPage = totalPages - 1;
196
- const start = _schedPage * SCHED_PER_PAGE;
197
- const pageItems = schedules.slice(start, start + SCHED_PER_PAGE);
198
-
199
- let html = '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>ID</th><th>Title</th><th>Schedule</th><th>Type</th><th>Project</th><th>Agent</th><th>Enabled</th><th>Last Run</th><th></th></tr></thead><tbody>';
200
- for (const s of pageItems) {
201
- const enabledBadge = s.enabled
202
- ? '<span class="pr-badge approved">enabled</span>'
203
- : '<span class="pr-badge rejected">disabled</span>';
204
- const lastRun = s._lastRun ? timeAgo(s._lastRun) : 'never';
205
- const typeBadge = '<span class="dispatch-type ' + escHtml(s.type || 'implement') + '">' + escHtml(s.type || 'implement') + '</span>';
206
- const humanCron = _cronToHuman(s.cron || '');
207
- html += '<tr style="cursor:pointer" onclick="openScheduleDetail(\'' + escHtml(s.id) + '\')">' +
208
- '<td><span class="pr-id">' + escHtml(s.id || '') + '</span></td>' +
209
- '<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml(s.title || '') + '">' + escHtml(s.title || '') + '</td>' +
210
- '<td><span title="' + escHtml(s.cron || '') + '" style="font-size:11px;color:var(--blue)">' + escHtml(humanCron) + '</span></td>' +
211
- '<td>' + typeBadge + '</td>' +
212
- '<td><span style="font-size:10px;color:var(--muted)">' + escHtml(s.project || '') + '</span></td>' +
213
- '<td><span class="pr-agent">' + escHtml(s.agent || 'auto') + '</span></td>' +
214
- '<td>' + enabledBadge + '</td>' +
215
- '<td><span class="pr-date">' + escHtml(lastRun) + '</span></td>' +
216
- '<td style="white-space:nowrap">' +
217
- '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:' + (s.enabled ? 'var(--yellow)' : 'var(--green)') + ';border-color:' + (s.enabled ? 'var(--yellow)' : 'var(--green)') + ';margin-right:4px" onclick="event.stopPropagation();toggleScheduleEnabled(\'' + escHtml(s.id) + '\',' + !s.enabled + ')" title="' + (s.enabled ? 'Disable' : 'Enable') + '">' + (s.enabled ? '&#x23F8;' : '&#x25B6;') + '</button>' +
218
- '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--blue);border-color:var(--blue);margin-right:4px" onclick="event.stopPropagation();openEditScheduleModal(\'' + escHtml(s.id) + '\')" title="Edit">&#x270E;</button>' +
219
- '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--red);border-color:var(--red)" onclick="event.stopPropagation();deleteSchedule(\'' + escHtml(s.id) + '\')" title="Delete">&#x2715;</button>' +
220
- '</td>' +
221
- '</tr>';
222
- }
223
- html += '</tbody></table></div>';
224
-
225
- if (schedules.length > SCHED_PER_PAGE) {
226
- html += '<div class="pr-pager">' +
227
- '<span class="pr-page-info">Showing ' + (start+1) + ' to ' + Math.min(start+SCHED_PER_PAGE, schedules.length) + ' of ' + schedules.length + '</span>' +
228
- '<div class="pr-pager-btns">' +
229
- '<button class="pr-pager-btn ' + (_schedPage === 0 ? 'disabled' : '') + '" onclick="_schedPrev()">Prev</button>' +
230
- '<button class="pr-pager-btn ' + (_schedPage >= totalPages-1 ? 'disabled' : '') + '" onclick="_schedNext()">Next</button>' +
231
- '</div>' +
232
- '</div>';
277
+ let html = _renderViewToggle();
278
+
279
+ if (_schedViewMode === 'calendar') {
280
+ html += _renderScheduleCalendar(schedules);
281
+ } else {
282
+ const totalPages = Math.ceil(schedules.length / SCHED_PER_PAGE);
283
+ if (_schedPage >= totalPages) _schedPage = totalPages - 1;
284
+ const start = _schedPage * SCHED_PER_PAGE;
285
+ const pageItems = schedules.slice(start, start + SCHED_PER_PAGE);
286
+
287
+ html += '<div class="pr-table-wrap"><table class="pr-table"><thead><tr><th>ID</th><th>Title</th><th>Schedule</th><th>Type</th><th>Project</th><th>Agent</th><th>Enabled</th><th>Last Run</th><th></th></tr></thead><tbody>';
288
+ for (const s of pageItems) {
289
+ const enabledBadge = s.enabled
290
+ ? '<span class="pr-badge approved">enabled</span>'
291
+ : '<span class="pr-badge rejected">disabled</span>';
292
+ const lastRun = s._lastRun ? timeAgo(s._lastRun) : 'never';
293
+ const typeBadge = '<span class="dispatch-type ' + escHtml(s.type || 'implement') + '">' + escHtml(s.type || 'implement') + '</span>';
294
+ const humanCron = _cronToHuman(s.cron || '');
295
+ html += '<tr style="cursor:pointer" onclick="openScheduleDetail(\'' + escHtml(s.id) + '\')">' +
296
+ '<td><span class="pr-id">' + escHtml(s.id || '') + '</span></td>' +
297
+ '<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml(s.title || '') + '">' + escHtml(s.title || '') + '</td>' +
298
+ '<td><span title="' + escHtml(s.cron || '') + '" style="font-size:11px;color:var(--blue)">' + escHtml(humanCron) + '</span></td>' +
299
+ '<td>' + typeBadge + '</td>' +
300
+ '<td><span style="font-size:10px;color:var(--muted)">' + escHtml(s.project || '') + '</span></td>' +
301
+ '<td><span class="pr-agent">' + escHtml(s.agent || 'auto') + '</span></td>' +
302
+ '<td>' + enabledBadge + '</td>' +
303
+ '<td><span class="pr-date">' + escHtml(lastRun) + '</span></td>' +
304
+ '<td style="white-space:nowrap">' +
305
+ '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:' + (s.enabled ? 'var(--yellow)' : 'var(--green)') + ';border-color:' + (s.enabled ? 'var(--yellow)' : 'var(--green)') + ';margin-right:4px" onclick="event.stopPropagation();toggleScheduleEnabled(\'' + escHtml(s.id) + '\',' + !s.enabled + ')" title="' + (s.enabled ? 'Disable' : 'Enable') + '">' + (s.enabled ? '&#x23F8;' : '&#x25B6;') + '</button>' +
306
+ '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--blue);border-color:var(--blue);margin-right:4px" onclick="event.stopPropagation();openEditScheduleModal(\'' + escHtml(s.id) + '\')" title="Edit">&#x270E;</button>' +
307
+ '<button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--red);border-color:var(--red)" onclick="event.stopPropagation();deleteSchedule(\'' + escHtml(s.id) + '\')" title="Delete">&#x2715;</button>' +
308
+ '</td>' +
309
+ '</tr>';
310
+ }
311
+ html += '</tbody></table></div>';
312
+
313
+ if (schedules.length > SCHED_PER_PAGE) {
314
+ html += '<div class="pr-pager">' +
315
+ '<span class="pr-page-info">Showing ' + (start+1) + ' to ' + Math.min(start+SCHED_PER_PAGE, schedules.length) + ' of ' + schedules.length + '</span>' +
316
+ '<div class="pr-pager-btns">' +
317
+ '<button class="pr-pager-btn ' + (_schedPage === 0 ? 'disabled' : '') + '" onclick="_schedPrev()">Prev</button>' +
318
+ '<button class="pr-pager-btn ' + (_schedPage >= totalPages-1 ? 'disabled' : '') + '" onclick="_schedNext()">Next</button>' +
319
+ '</div>' +
320
+ '</div>';
321
+ }
233
322
  }
234
323
 
235
324
  el.innerHTML = html;
@@ -463,4 +552,4 @@ async function deleteSchedule(id) {
463
552
  // Expose _generateScheduleId globally for the inline oninput handler
464
553
  window._generateScheduleId = _generateScheduleId;
465
554
 
466
- window.MinionsSchedules = { renderSchedules, openCreateScheduleModal, openEditScheduleModal, openScheduleDetail, submitSchedule, toggleScheduleEnabled, deleteSchedule, _cronToHuman, _parseNaturalCron, _toggleCronMode, _quickSelectDays, _toggleDayPill, _updateCronPreview, _schedPrev, _schedNext };
555
+ window.MinionsSchedules = { renderSchedules, openCreateScheduleModal, openEditScheduleModal, openScheduleDetail, submitSchedule, toggleScheduleEnabled, deleteSchedule, _cronToHuman, _parseNaturalCron, _toggleCronMode, _quickSelectDays, _toggleDayPill, _updateCronPreview, _schedPrev, _schedNext, _schedSetView };
@@ -187,7 +187,7 @@ function renderMd(s) {
187
187
  // 4. Restore code placeholders
188
188
  html = html.replace(/\x00CB(\d+)\x00/g, function(_, idx) { return codeSlots[idx]; });
189
189
 
190
- return html;
190
+ return '<div class="md-content">' + html + '</div>';
191
191
  }
192
192
 
193
193
  window.MinionsUtils = { wakeEngine, escHtml, renderMd, normalizePlanFile, timeAgo, statusColor, llmCopyBtn, copyLlmText };
@@ -131,6 +131,14 @@
131
131
  .token-bar:hover .token-bar-tip { display: block; }
132
132
  .token-chart-labels { display: flex; gap: 3px; }
133
133
  .token-chart-labels span { flex: 1; min-width: 8px; max-width: 24px; font-size: 8px; color: var(--muted); text-align: center; overflow: hidden; }
134
+ /* Schedule calendar */
135
+ .sched-cal { display: grid; grid-template-columns: 50px repeat(7, 1fr); gap: 1px; background: var(--border); border-radius: var(--radius-md); overflow: hidden; }
136
+ .sched-cal-header { background: var(--surface2); padding: 6px 4px; text-align: center; font-size: 10px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }
137
+ .sched-cal-hour { background: var(--surface); padding: 6px 6px 6px 0; font-size: 10px; color: var(--muted); text-align: right; font-variant-numeric: tabular-nums; display: flex; align-items: flex-start; justify-content: flex-end; }
138
+ .sched-cal-cell { background: var(--surface); padding: 3px; min-height: 44px; display: flex; flex-direction: column; gap: 2px; }
139
+ .sched-cal-slot { padding: 3px 6px; border-radius: 3px; font-size: 10px; cursor: pointer; border-left: 3px solid; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; transition: filter 0.15s; }
140
+ .sched-cal-slot:hover { filter: brightness(1.2); }
141
+
134
142
  .token-agent-table { width: 100%; margin-top: var(--space-5); }
135
143
  .token-agent-table th { text-align: right; font-size: var(--text-sm); color: var(--muted); font-weight: 500; padding: var(--space-2) var(--space-4); border-bottom: 1px solid var(--border); white-space: nowrap; }
136
144
  .token-agent-table th:first-child { text-align: left; }
@@ -541,6 +549,14 @@
541
549
  }
542
550
  .detail-content h4 { color: var(--text); font-size: var(--text-lg); margin: var(--space-6) 0 var(--space-3) 0; font-family: 'Segoe UI', sans-serif; }
543
551
  .detail-content .section { margin-bottom: var(--space-7); padding: var(--space-6); background: var(--surface2); border: 1px solid var(--border); border-radius: var(--radius-md); }
552
+
553
+ /* Markdown content — consistent styling everywhere renderMd() is used */
554
+ .md-content { font-family: 'Segoe UI', system-ui, sans-serif; font-size: 12px; line-height: 1.6; white-space: normal; word-break: break-word; }
555
+ .md-content pre { font-family: Consolas, 'Courier New', monospace; white-space: pre-wrap; }
556
+ .md-content code { font-family: Consolas, 'Courier New', monospace; }
557
+ .md-content table { border-collapse: collapse; width: 100%; margin: 6px 0; }
558
+ .md-content th, .md-content td { padding: 4px 8px; border: 1px solid var(--border); text-align: left; font-size: 11px; }
559
+ .md-content th { background: var(--surface); font-weight: 600; }
544
560
  .status-line { display: flex; align-items: center; gap: var(--space-5); padding: var(--space-5) var(--space-7); background: var(--bg); border-bottom: 1px solid var(--border); font-size: var(--text-md); }
545
561
 
546
562
  /* Modal for inbox detail */
package/dashboard.js CHANGED
@@ -747,7 +747,7 @@ function spawnEngine() {
747
747
  if (key === 'CLAUDECODE' || key.startsWith('CLAUDE_CODE') || key.startsWith('CLAUDECODE_')) delete childEnv[key];
748
748
  }
749
749
  const engineProc = cpSpawn(process.execPath, [path.join(MINIONS_DIR, 'engine.js'), 'start'], {
750
- cwd: MINIONS_DIR, stdio: 'ignore', detached: true, env: childEnv,
750
+ cwd: MINIONS_DIR, stdio: 'ignore', detached: true, env: childEnv, windowsHide: true,
751
751
  });
752
752
  engineProc.unref();
753
753
  return engineProc.pid;
@@ -758,7 +758,7 @@ function killEnginePid(pid) {
758
758
  try {
759
759
  const safePid = shared.validatePid(pid);
760
760
  if (process.platform === 'win32') {
761
- execFileSync('taskkill', ['/PID', String(safePid), '/F', '/T'], { stdio: 'pipe', timeout: 5000 });
761
+ execFileSync('taskkill', ['/PID', String(safePid), '/F', '/T'], { stdio: 'pipe', timeout: 5000, windowsHide: true });
762
762
  } else {
763
763
  process.kill(safePid, 'SIGKILL');
764
764
  }
@@ -1271,7 +1271,7 @@ const server = http.createServer(async (req, res) => {
1271
1271
  try {
1272
1272
  const safePid = shared.validatePid(status.pid);
1273
1273
  if (process.platform === 'win32') {
1274
- require('child_process').execFileSync('taskkill', ['/PID', String(safePid), '/F', '/T'], { stdio: 'pipe', timeout: 5000 });
1274
+ require('child_process').execFileSync('taskkill', ['/PID', String(safePid), '/F', '/T'], { stdio: 'pipe', timeout: 5000, windowsHide: true });
1275
1275
  } else {
1276
1276
  process.kill(safePid, 'SIGTERM');
1277
1277
  }
@@ -1866,7 +1866,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
1866
1866
  try {
1867
1867
  const safePid = shared.validatePid(agentStatus.pid);
1868
1868
  if (process.platform === 'win32') {
1869
- require('child_process').execFileSync('taskkill', ['/PID', String(safePid), '/F', '/T'], { stdio: 'pipe', timeout: 5000 });
1869
+ require('child_process').execFileSync('taskkill', ['/PID', String(safePid), '/F', '/T'], { stdio: 'pipe', timeout: 5000, windowsHide: true });
1870
1870
  } else {
1871
1871
  process.kill(safePid, 'SIGTERM');
1872
1872
  }
@@ -2567,7 +2567,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
2567
2567
  try {
2568
2568
  const safePid = shared.validatePid(agentStatus.pid);
2569
2569
  if (process.platform === 'win32') {
2570
- require('child_process').execFileSync('taskkill', ['/PID', String(safePid), '/F', '/T'], { stdio: 'pipe', timeout: 5000 });
2570
+ require('child_process').execFileSync('taskkill', ['/PID', String(safePid), '/F', '/T'], { stdio: 'pipe', timeout: 5000, windowsHide: true });
2571
2571
  } else {
2572
2572
  process.kill(safePid, 'SIGTERM');
2573
2573
  }
@@ -3602,7 +3602,7 @@ server.listen(PORT, '127.0.0.1', () => {
3602
3602
  let alive = false;
3603
3603
  try {
3604
3604
  if (process.platform === 'win32') {
3605
- const out = execSync(`tasklist /FI "PID eq ${control.pid}" /NH`, { encoding: 'utf8', timeout: 3000 });
3605
+ const out = execSync(`tasklist /FI "PID eq ${control.pid}" /NH`, { encoding: 'utf8', timeout: 3000, windowsHide: true });
3606
3606
  alive = out.includes(String(control.pid));
3607
3607
  } else {
3608
3608
  process.kill(control.pid, 0); // signal 0 = check existence
@@ -33,6 +33,7 @@ const child = spawn(process.platform === 'win32' ? 'npx.cmd' : 'npx', [
33
33
  stdio: 'inherit',
34
34
  env: { ...process.env, AZURE_DEVOPS_EXT_PAT: token },
35
35
  windowsHide: true,
36
+ shell: false,
36
37
  });
37
38
 
38
39
  child.on('exit', (code) => process.exit(code || 0));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.122",
3
+ "version": "0.1.124",
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"