@yemi33/minions 0.1.1677 → 0.1.1678

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,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1678 (2026-05-02)
4
+
5
+ ### Fixes
6
+ - dashboard screen audit — bug fixes across 11 renderers
7
+
3
8
  ## 0.1.1677 (2026-05-02)
4
9
 
5
10
  ### Fixes
@@ -39,7 +39,7 @@ function renderAgents(agents) {
39
39
  agentData = agents;
40
40
  const grid = document.getElementById('agents-grid');
41
41
  grid.innerHTML = agents.map(a => `
42
- <div class="agent-card ${statusColor(a.status)}" onclick="if(shouldIgnoreSelectionClick(event))return;openAgentDetail('${escapeHtml(a.id)}')">
42
+ <div class="agent-card ${statusColor(a.status)}" data-agent-id="${escapeHtml(a.id)}" onclick="if(shouldIgnoreSelectionClick(event))return;openAgentDetail(this.dataset.agentId)">
43
43
  <div class="agent-card-header">
44
44
  <span class="agent-name"><span class="agent-emoji">${escapeHtml(a.emoji)}</span>${escapeHtml(a.name)}${_runtimeTagHtml(a.runtime)}</span>
45
45
  <span class="status-badge ${escapeHtml(a.status)}">${escapeHtml(a.status)}</span>
@@ -116,7 +116,7 @@ async function openAgentDetail(id) {
116
116
  document.getElementById('detail-content').innerHTML =
117
117
  '<div style="padding:24px;text-align:center">' +
118
118
  '<div style="color:var(--red);margin-bottom:12px">Error loading agent detail: ' + escapeHtml(e.message) + '</div>' +
119
- '<button onclick="openAgentDetail(\'' + escapeHtml(id) + '\')" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer;font-size:12px">Retry</button>' +
119
+ '<button data-agent-id="' + escapeHtml(id) + '" onclick="openAgentDetail(this.dataset.agentId)" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer;font-size:12px">Retry</button>' +
120
120
  ' <button onclick="closeDetail()" style="padding:6px 16px;background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius-sm);cursor:pointer;font-size:12px;color:var(--text)">Close</button>' +
121
121
  '</div>';
122
122
  }
@@ -18,12 +18,7 @@ function renderEngineStatus(engine) {
18
18
  // Detect stale engine — says running but heartbeat is old (>2 min)
19
19
  if (state === 'running' && engine?.heartbeat) {
20
20
  staleMs = Date.now() - engine.heartbeat;
21
- if (staleMs > 120000) {
22
- state = 'stale';
23
- }
24
- } else if (state === 'running' && !engine?.heartbeat) {
25
- // Running but no heartbeat yet — engine just started or old version
26
- state = 'running';
21
+ if (staleMs > 120000) state = 'stale';
27
22
  }
28
23
 
29
24
  badge.className = 'engine-badge ' + (state === 'stale' ? 'stopped' : state);
@@ -172,12 +167,12 @@ function renderDispatch(dispatch) {
172
167
  '</tr>';
173
168
  }).join('') + '</tbody></table>';
174
169
  if (completed.length > COMPLETED_PER_PAGE) {
175
- completedEl.innerHTML += '<div class="pr-pager">' +
170
+ completedEl.insertAdjacentHTML('beforeend', '<div class="pr-pager">' +
176
171
  '<span class="pr-page-info">Showing ' + (compStart + 1) + ' to ' + Math.min(compStart + COMPLETED_PER_PAGE, completed.length) + ' of ' + completed.length + '</span>' +
177
172
  '<div class="pr-pager-btns">' +
178
173
  '<button class="pr-pager-btn ' + (_completedPage === 0 ? 'disabled' : '') + '" onclick="_completedPrev()">Prev</button>' +
179
174
  '<button class="pr-pager-btn ' + (_completedPage >= totalCompPages - 1 ? 'disabled' : '') + '" onclick="_completedNext()">Next</button>' +
180
- '</div></div>';
175
+ '</div></div>');
181
176
  }
182
177
  } else {
183
178
  completedEl.innerHTML = '<p class="empty">No completed dispatches yet.</p>';
@@ -206,12 +201,12 @@ function renderEngineLog(log) {
206
201
  '</div>'
207
202
  ).join('');
208
203
  if (reversed.length > LOG_PER_PAGE) {
209
- el.innerHTML += '<div class="pr-pager">' +
204
+ el.insertAdjacentHTML('beforeend', '<div class="pr-pager">' +
210
205
  '<span class="pr-page-info">Showing ' + (logStart + 1) + ' to ' + Math.min(logStart + LOG_PER_PAGE, reversed.length) + ' of ' + reversed.length + '</span>' +
211
206
  '<div class="pr-pager-btns">' +
212
207
  '<button class="pr-pager-btn ' + (_logPage === 0 ? 'disabled' : '') + '" onclick="_logPrev()">Prev</button>' +
213
208
  '<button class="pr-pager-btn ' + (_logPage >= totalLogPages - 1 ? 'disabled' : '') + '" onclick="_logNext()">Next</button>' +
214
- '</div></div>';
209
+ '</div></div>');
215
210
  }
216
211
  }
217
212
 
@@ -228,7 +223,7 @@ async function showErrorDetails(agentId, reason, task) {
228
223
  document.getElementById('modal').classList.add('open');
229
224
 
230
225
  try {
231
- const output = await fetch('/api/agent/' + agentId + '/output').then(r => r.text());
226
+ const output = await safeFetch('/api/agent/' + agentId + '/output').then(r => r.text());
232
227
  const lines = output.split('\n');
233
228
  const stderrIdx = lines.findIndex(l => l.startsWith('## stderr'));
234
229
  let summary = '';
@@ -43,12 +43,12 @@ function renderInbox(inbox) {
43
43
  </div>`;
44
44
  }).join('');
45
45
  if (inbox.length > INBOX_PER_PAGE) {
46
- list.innerHTML += '<div class="pr-pager">' +
46
+ list.insertAdjacentHTML('beforeend', '<div class="pr-pager">' +
47
47
  '<span class="pr-page-info">Showing ' + (inboxStart + 1) + ' to ' + Math.min(inboxStart + INBOX_PER_PAGE, inbox.length) + ' of ' + inbox.length + '</span>' +
48
48
  '<div class="pr-pager-btns">' +
49
49
  '<button class="pr-pager-btn ' + (_inboxPage === 0 ? 'disabled' : '') + '" onclick="_inboxPrev()">Prev</button>' +
50
50
  '<button class="pr-pager-btn ' + (_inboxPage >= totalInboxPages - 1 ? 'disabled' : '') + '" onclick="_inboxNext()">Next</button>' +
51
- '</div></div>';
51
+ '</div></div>');
52
52
  }
53
53
  restoreNotifBadges();
54
54
  }
@@ -134,12 +134,12 @@ function renderKnowledgeBase() {
134
134
  '</div>';
135
135
  }).join('');
136
136
  if (items.length > KB_PER_PAGE) {
137
- listEl.innerHTML += '<div class="pr-pager">' +
137
+ listEl.insertAdjacentHTML('beforeend', '<div class="pr-pager">' +
138
138
  '<span class="pr-page-info">Showing ' + (kbStart + 1) + ' to ' + Math.min(kbStart + KB_PER_PAGE, items.length) + ' of ' + items.length + '</span>' +
139
139
  '<div class="pr-pager-btns">' +
140
140
  '<button class="pr-pager-btn ' + (_kbPage === 0 ? 'disabled' : '') + '" onclick="_kbPrev()">Prev</button>' +
141
141
  '<button class="pr-pager-btn ' + (_kbPage >= totalKbPages - 1 ? 'disabled' : '') + '" onclick="_kbNext()">Next</button>' +
142
- '</div></div>';
142
+ '</div></div>');
143
143
  }
144
144
  restoreNotifBadges();
145
145
  }
@@ -242,18 +242,18 @@ function openCreateKbModal() {
242
242
  '<textarea id="kb-new-content" rows="8" 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="Write your knowledge entry..."></textarea></label>' +
243
243
  '<div style="display:flex;justify-content:flex-end;gap:8px">' +
244
244
  '<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
245
- '<button onclick="submitKbEntry()" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Save</button>' +
245
+ '<button onclick="submitKbEntry(event)" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Save</button>' +
246
246
  '</div>' +
247
247
  '</div>';
248
248
  document.getElementById('modal').classList.add('open');
249
249
  }
250
250
 
251
- async function submitKbEntry() {
252
- var btn = event?.target; if (btn) { btn.disabled = true; btn.textContent = 'Creating...'; }
251
+ async function submitKbEntry(e) {
252
+ var btn = (e || window.event)?.target; if (btn) { btn.disabled = true; btn.textContent = 'Saving...'; }
253
253
  const category = document.getElementById('kb-new-category').value;
254
254
  const title = document.getElementById('kb-new-title').value;
255
255
  const content = document.getElementById('kb-new-content').value;
256
- if (!title || !content) { if (btn) { btn.disabled = false; btn.textContent = 'Create'; } alert('Title and content are required'); return; }
256
+ if (!title || !content) { if (btn) { btn.disabled = false; btn.textContent = 'Save'; } alert('Title and content are required'); return; }
257
257
  try {
258
258
  showToast('cmd-toast', 'KB entry created', true);
259
259
  const res = await fetch('/api/knowledge', {
@@ -261,8 +261,8 @@ async function submitKbEntry() {
261
261
  body: JSON.stringify({ category, title, content })
262
262
  });
263
263
  if (res.ok) { closeModal(); refreshKnowledgeBase(); }
264
- else { if (btn) { btn.disabled = false; btn.textContent = 'Create'; } const d = await res.json().catch(() => ({})); showToast('cmd-toast', 'KB create failed: ' + (d.error || 'unknown'), false); }
265
- } catch (e) { if (btn) { btn.disabled = false; btn.textContent = 'Create'; } showToast('cmd-toast', 'Error: ' + e.message, false); }
264
+ else { if (btn) { btn.disabled = false; btn.textContent = 'Save'; } const d = await res.json().catch(() => ({})); showToast('cmd-toast', 'KB create failed: ' + (d.error || 'unknown'), false); }
265
+ } catch (err) { if (btn) { btn.disabled = false; btn.textContent = 'Save'; } showToast('cmd-toast', 'Error: ' + err.message, false); }
266
266
  }
267
267
 
268
268
  async function kbOpenItem(category, file) {
@@ -26,7 +26,7 @@ function renderMeetings(meetings) {
26
26
  const visible = _showArchived ? meetings : active;
27
27
  if (visible.length === 0) {
28
28
  el.innerHTML = '<p class="empty">No active meetings.</p>';
29
- 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>';
29
+ if (archived.length) el.insertAdjacentHTML('beforeend', '<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>');
30
30
  return;
31
31
  }
32
32
 
@@ -66,17 +66,17 @@ function renderMeetings(meetings) {
66
66
  }).join('');
67
67
 
68
68
  if (visible.length > MTG_PER_PAGE) {
69
- el.innerHTML += '<div class="pr-pager">' +
69
+ el.insertAdjacentHTML('beforeend', '<div class="pr-pager">' +
70
70
  '<span class="pr-page-info">Showing ' + (start + 1) + ' to ' + Math.min(start + MTG_PER_PAGE, visible.length) + ' of ' + visible.length + '</span>' +
71
71
  '<div class="pr-pager-btns">' +
72
72
  '<button class="pr-pager-btn ' + (_mtgPage === 0 ? 'disabled' : '') + '" onclick="_mtgPrev()">Prev</button>' +
73
73
  '<button class="pr-pager-btn ' + (_mtgPage >= totalPages - 1 ? 'disabled' : '') + '" onclick="_mtgNext()">Next</button>' +
74
- '</div></div>';
74
+ '</div></div>');
75
75
  }
76
76
 
77
77
  if (archived.length > 0) {
78
- el.innerHTML += '<div style="text-align:center;margin-top:8px"><button class="pr-pager-btn" style="font-size:10px" onclick="_toggleArchivedMeetings()">' +
79
- (_showArchived ? 'Hide' : 'Show') + ' ' + archived.length + ' archived</button></div>';
78
+ el.insertAdjacentHTML('beforeend', '<div style="text-align:center;margin-top:8px"><button class="pr-pager-btn" style="font-size:10px" onclick="_toggleArchivedMeetings()">' +
79
+ (_showArchived ? 'Hide' : 'Show') + ' ' + archived.length + ' archived</button></div>');
80
80
  }
81
81
  restoreNotifBadges();
82
82
  }
@@ -254,14 +254,14 @@ function openCreateMeetingModal() {
254
254
  '<div style="color:var(--text);font-size:var(--text-md)">Participants<div style="display:flex;flex-direction:column;gap:4px;margin-top:4px" id="mtg-participants">' + agentOpts + '</div></div>' +
255
255
  '<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:4px">' +
256
256
  '<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
257
- '<button onclick="_submitCreateMeeting()" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Start Meeting</button>' +
257
+ '<button onclick="_submitCreateMeeting(event)" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Start Meeting</button>' +
258
258
  '</div>' +
259
259
  '</div>';
260
260
  document.getElementById('modal').classList.add('open');
261
261
  }
262
262
 
263
- async function _submitCreateMeeting() {
264
- var btn = event?.target; if (btn) { btn.disabled = true; btn.textContent = 'Starting...'; }
263
+ async function _submitCreateMeeting(e) {
264
+ var btn = (e || window.event)?.target; if (btn) { btn.disabled = true; btn.textContent = 'Starting...'; }
265
265
  const title = document.getElementById('mtg-title')?.value?.trim();
266
266
  const agenda = document.getElementById('mtg-agenda')?.value?.trim();
267
267
  if (!title || !agenda) { if (btn) { btn.disabled = false; btn.textContent = 'Start Meeting'; } alert('Title and agenda required'); return; }
@@ -4,7 +4,7 @@ function renderPinned(entries) {
4
4
  entries = (entries || []).filter(function(e) { return !isDeleted('pin:' + e.title); });
5
5
  const el = document.getElementById('pinned-content');
6
6
  if (!el) return;
7
- if (!entries || entries.length === 0) {
7
+ if (entries.length === 0) {
8
8
  el.innerHTML = '<p class="empty">No pinned notes. Pin important context that all agents should see.</p>';
9
9
  return;
10
10
  }
@@ -40,7 +40,7 @@ async function submitPinnedNote(e) {
40
40
  const title = document.getElementById('pin-title').value;
41
41
  const content = document.getElementById('pin-content').value;
42
42
  const level = document.getElementById('pin-level').value;
43
- if (!title || !content) { if (btn) { btn.disabled = false; btn.textContent = 'Pin Note'; } alert('Title and content required'); return; }
43
+ if (!title || !content) { if (btn) { btn.disabled = false; btn.textContent = 'Pin'; } alert('Title and content required'); return; }
44
44
  try { closeModal(); } catch { /* may not be open */ }
45
45
 
46
46
  // Optimistic render: append the new entry to the pinned list and re-render immediately
@@ -21,15 +21,15 @@ function openCreatePlanModal() {
21
21
  '<div style="font-size:11px;color:var(--muted)">After creating, click Execute on the plan card to have an agent convert it into a PRD with work items.</div>' +
22
22
  '<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:4px">' +
23
23
  '<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
24
- '<button onclick="_submitCreatePlan()" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Create Plan</button>' +
24
+ '<button onclick="_submitCreatePlan(event)" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Create Plan</button>' +
25
25
  '</div>' +
26
26
  '</div>';
27
27
  document.getElementById('modal').classList.add('open');
28
28
  setTimeout(() => document.getElementById('plan-new-title')?.focus(), 100);
29
29
  }
30
30
 
31
- async function _submitCreatePlan() {
32
- var btn = event?.target; if (btn) { btn.disabled = true; btn.textContent = 'Creating...'; }
31
+ async function _submitCreatePlan(e) {
32
+ var btn = (e || window.event)?.target; if (btn) { btn.disabled = true; btn.textContent = 'Creating...'; }
33
33
  const title = document.getElementById('plan-new-title')?.value?.trim();
34
34
  const content = document.getElementById('plan-new-content')?.value?.trim();
35
35
  if (!title) { if (btn) { btn.disabled = false; btn.textContent = 'Create Plan'; } alert('Title is required'); return; }
@@ -144,16 +144,13 @@ function renderPlans(plans) {
144
144
  }
145
145
  }
146
146
 
147
- // Link .md plans to their PRD .json — if a PRD is being worked on, the source plan is too
148
- // Convention: plan-w025-2026-03-15.md → officeagent-2026-03-15.json (same date, different prefix)
147
+ // Link .md plans to their PRD .json — if a PRD is being worked on, the source plan is too.
148
+ // The .md → .json mapping is materialized in planToPrdFile below; here we only need to
149
+ // know whether any PRD is active to mark all candidate source .md plans as working.
149
150
  const workingJsons = new Set([...workingPlanFiles].filter(f => f.endsWith('.json')));
150
151
  if (workingJsons.size > 0) {
151
152
  for (const p of plans) {
152
- if (p.format === 'draft' && p.file.endsWith('.md')) {
153
- // A .md plan is "working" if any PRD .json has active dispatches
154
- // (since the .md is the source that generated those PRDs)
155
- if (workingJsons.size > 0) workingPlanFiles.add(p.file);
156
- }
153
+ if (p.format === 'draft' && p.file.endsWith('.md')) workingPlanFiles.add(p.file);
157
154
  }
158
155
  }
159
156
 
@@ -134,15 +134,15 @@ function openAddPrModal() {
134
134
  '<div style="font-size:11px;color:var(--muted);margin-top:-4px;padding-left:24px">Off = context only (e.g. teammate\'s PR). On = agents actively monitor and fix issues.</div>' +
135
135
  '<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:8px">' +
136
136
  '<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
137
- '<button onclick="_submitLinkPr()" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Link PR</button>' +
137
+ '<button onclick="_submitLinkPr(event)" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Link PR</button>' +
138
138
  '</div>' +
139
139
  '</div>';
140
140
  document.getElementById('modal').classList.add('open');
141
141
  setTimeout(() => document.getElementById('pr-link-url')?.focus(), 100);
142
142
  }
143
143
 
144
- async function _submitLinkPr() {
145
- var btn = event?.target; if (btn) { btn.disabled = true; btn.textContent = 'Linking...'; }
144
+ async function _submitLinkPr(e) {
145
+ var btn = (e || window.event)?.target; if (btn) { btn.disabled = true; btn.textContent = 'Linking...'; }
146
146
  const url = document.getElementById('pr-link-url')?.value?.trim();
147
147
  if (!url) { if (btn) { btn.disabled = false; btn.textContent = 'Link PR'; } alert('PR URL is required'); return; }
148
148
  const title = document.getElementById('pr-link-title')?.value?.trim() || '';
@@ -166,7 +166,8 @@ async function unlinkPr(id) {
166
166
  if (!confirm('Remove ' + id + ' from tracking?')) return;
167
167
  showToast('cmd-toast', id + ' removed', true);
168
168
  markDeleted('pr:' + id);
169
- const row = document.querySelector('[data-pr-id="' + id + '"]')?.closest('tr');
169
+ const escId = window.CSS && CSS.escape ? CSS.escape(id) : id;
170
+ const row = document.querySelector('[data-pr-id="' + escId + '"]')?.closest('tr');
170
171
  if (row) row.remove();
171
172
  try {
172
173
  const res = await fetch('/api/pull-requests/delete', {
@@ -292,7 +292,7 @@ function renderSchedules(schedules) {
292
292
  const lastRun = s._lastRun ? timeAgo(s._lastRun) : 'never';
293
293
  const typeBadge = '<span class="dispatch-type ' + escHtml(s.type || 'implement') + '">' + escHtml(s.type || 'implement') + '</span>';
294
294
  const humanCron = _cronToHuman(s.cron || '');
295
- html += '<tr style="cursor:pointer" onclick="if(shouldIgnoreSelectionClick(event))return;openScheduleDetail(\'' + escHtml(s.id) + '\')">' +
295
+ html += '<tr data-sched-id="' + escHtml(s.id || '') + '" style="cursor:pointer" onclick="if(shouldIgnoreSelectionClick(event))return;openScheduleDetail(\'' + escHtml(s.id) + '\')">' +
296
296
  '<td><span class="pr-id">' + escHtml(s.id || '') + '</span></td>' +
297
297
  '<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml(s.title || '') + '">' + escHtml(s.title || '') + '</td>' +
298
298
  '<td><span title="' + escHtml(s.cron || '') + '" style="font-size:11px;color:var(--blue)">' + escHtml(humanCron) + '</span></td>' +
@@ -459,7 +459,7 @@ function _scheduleFormHtml(sched, isEdit) {
459
459
  '</label>' +
460
460
  '<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:8px">' +
461
461
  '<button onclick="closeModal()" class="pr-pager-btn" style="padding:6px 16px;font-size:var(--text-md)">Cancel</button>' +
462
- '<button onclick="submitSchedule(' + isEdit + ')" style="padding:6px 16px;font-size:var(--text-md);background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">' + (isEdit ? 'Save' : 'Create') + '</button>' +
462
+ '<button onclick="submitSchedule(' + isEdit + ',event)" style="padding:6px 16px;font-size:var(--text-md);background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">' + (isEdit ? 'Save' : 'Create') + '</button>' +
463
463
  '</div>' +
464
464
  '</div>';
465
465
  }
@@ -487,8 +487,8 @@ function openEditScheduleModal(id) {
487
487
  _updateCronPreview();
488
488
  }
489
489
 
490
- async function submitSchedule(isEdit) {
491
- var btn = event?.target; if (btn) { btn.disabled = true; btn.textContent = isEdit ? 'Saving...' : 'Creating...'; }
490
+ async function submitSchedule(isEdit, e) {
491
+ var btn = (e || window.event)?.target; if (btn) { btn.disabled = true; btn.textContent = isEdit ? 'Saving...' : 'Creating...'; }
492
492
  _showScheduleError('');
493
493
  const title = document.getElementById('sched-edit-title').value.trim();
494
494
  const cron = window._schedComputedCron || '';
@@ -505,7 +505,7 @@ async function submitSchedule(isEdit) {
505
505
  id = _generateScheduleId(title);
506
506
  }
507
507
 
508
- function _resetSchedBtn() { if (btn) { btn.disabled = false; btn.textContent = isEdit ? 'Save Changes' : 'Create Schedule'; } }
508
+ function _resetSchedBtn() { if (btn) { btn.disabled = false; btn.textContent = isEdit ? 'Save' : 'Create'; } }
509
509
  if (!title) { _resetSchedBtn(); _showScheduleError('Title is required'); return; }
510
510
  if (!cron) { _resetSchedBtn(); _showScheduleError('Schedule is required \u2014 select days and time, or use natural language'); return; }
511
511
 
@@ -523,9 +523,15 @@ async function submitSchedule(isEdit) {
523
523
  } catch (e) { _resetSchedBtn(); _showScheduleError('Error: ' + e.message); }
524
524
  }
525
525
 
526
+ function _findSchedRow(id) {
527
+ var sel = 'tr[data-sched-id="' + (window.CSS && CSS.escape ? CSS.escape(id) : id) + '"]';
528
+ return document.querySelector(sel);
529
+ }
530
+
526
531
  async function toggleScheduleEnabled(id, enabled) {
527
532
  // Optimistic toggle — swap badge text immediately
528
- document.querySelectorAll('tr').forEach(function(r) { if (r.textContent.includes(id)) { var badge = r.querySelector('.status-badge'); if (badge) badge.textContent = enabled ? 'ENABLED' : 'DISABLED'; } });
533
+ var row = _findSchedRow(id);
534
+ if (row) { var badge = row.querySelector('.status-badge'); if (badge) badge.textContent = enabled ? 'ENABLED' : 'DISABLED'; }
529
535
  try {
530
536
  const res = await fetch('/api/schedules/update', {
531
537
  method: 'POST', headers: { 'Content-Type': 'application/json' },
@@ -542,7 +548,8 @@ async function deleteSchedule(id) {
542
548
  if (!confirm('Delete scheduled task "' + id + '"?')) return;
543
549
  showToast('cmd-toast', 'Schedule deleted', true);
544
550
  markDeleted('sched:' + id);
545
- document.querySelectorAll('tr').forEach(function(r) { if (r.textContent.includes(id)) r.remove(); });
551
+ var row = _findSchedRow(id);
552
+ if (row) row.remove();
546
553
  try {
547
554
  const res = await fetch('/api/schedules/delete', {
548
555
  method: 'POST', headers: { 'Content-Type': 'application/json' },
@@ -184,8 +184,10 @@ function toggleWatchPause(id, pause) {
184
184
  method: 'POST',
185
185
  headers: { 'Content-Type': 'application/json' },
186
186
  body: JSON.stringify({ id: id, status: newStatus })
187
- }).then(function(res) { return res.json(); }).then(function(data) {
188
- if (data.error) showToast('cmd-toast', 'Error: ' + data.error, false);
187
+ }).then(async function(res) {
188
+ var data = await res.json().catch(function() { return {}; });
189
+ if (!res.ok || data.error) showToast('cmd-toast', 'Error: ' + (data.error || ('HTTP ' + res.status)), false);
190
+ else if (typeof refresh === 'function') refresh();
189
191
  }).catch(function(err) {
190
192
  showToast('cmd-toast', 'Error: ' + err.message, false);
191
193
  });
@@ -297,12 +299,14 @@ function submitWatch() {
297
299
  stopAfter: stopAfter,
298
300
  onNotMet: onNotMet || null,
299
301
  })
300
- }).then(function(res) { return res.json(); }).then(function(data) {
301
- if (data.error) {
302
- showToast('cmd-toast', 'Error: ' + data.error, false);
302
+ }).then(async function(res) {
303
+ var data = await res.json().catch(function() { return {}; });
304
+ if (!res.ok || data.error) {
305
+ showToast('cmd-toast', 'Error: ' + (data.error || ('HTTP ' + res.status)), false);
303
306
  } else {
304
- showToast('cmd-toast', 'Watch created: ' + data.watch.id, true);
307
+ showToast('cmd-toast', 'Watch created: ' + (data.watch && data.watch.id || ''), true);
305
308
  closeModal();
309
+ if (typeof refresh === 'function') refresh();
306
310
  }
307
311
  }).catch(function(err) {
308
312
  showToast('cmd-toast', 'Error: ' + err.message, false);
@@ -40,7 +40,7 @@ function wiRow(item) {
40
40
  : (item.branchStrategy === 'shared-branch' && item.status === 'done')
41
41
  ? '<span style="font-size:9px;color:var(--muted)" title="Part of shared branch — aggregate PR created at verify stage">shared branch</span>'
42
42
  : '<span style="color:var(--muted)">—</span>';
43
- return '<tr style="cursor:pointer" onclick="if(shouldIgnoreSelectionClick(event))return;openWorkItemDetail(\'' + escapeHtml(item.id) + '\')">' +
43
+ return '<tr data-wi-id="' + escapeHtml(item.id) + '" style="cursor:pointer" onclick="if(shouldIgnoreSelectionClick(event))return;openWorkItemDetail(\'' + escapeHtml(item.id) + '\')">' +
44
44
  '<td><span class="pr-id">' + escapeHtml(item.id || '') + '</span></td>' +
45
45
  '<td style="max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escapeHtml((item.title || '').slice(0, 200)) + '">' + escapeHtml(item.title || '') + '</td>' +
46
46
  '<td><span style="font-size:10px;color:var(--muted)">' + escapeHtml(item._source || '') + '</span>' +
@@ -195,12 +195,16 @@ async function submitWorkItemEdit(id, source, e) {
195
195
  } catch (e) { alert('Update error: ' + e.message); editWorkItem(id, source); }
196
196
  }
197
197
 
198
+ function _removeWiRow(id) {
199
+ var sel = 'tr[data-wi-id="' + (window.CSS && CSS.escape ? CSS.escape(id) : id) + '"]';
200
+ document.querySelectorAll(sel).forEach(function(r) { r.remove(); });
201
+ }
202
+
198
203
  async function deleteWorkItem(id, source) {
199
204
  if (!confirm('Delete work item ' + id + '? This will kill any running agent and remove all dispatch history.')) return;
200
205
  showToast('cmd-toast', 'Work item deleted', true);
201
206
  markDeleted('wi:' + id);
202
- var wiTable = document.getElementById('work-items-content');
203
- (wiTable || document).querySelectorAll('tr').forEach(function(r) { if (r.textContent.includes(id)) r.remove(); });
207
+ _removeWiRow(id);
204
208
  try {
205
209
  const res = await fetch('/api/work-items/delete', {
206
210
  method: 'POST', headers: { 'Content-Type': 'application/json' },
@@ -214,8 +218,7 @@ async function cancelWorkItem(id, source) {
214
218
  if (!confirm('Cancel work item ' + id + '? This will kill any running agent and mark it cancelled.')) return;
215
219
  showToast('cmd-toast', 'Work item cancelled', true);
216
220
  markDeleted('wi:' + id);
217
- var wiTable = document.getElementById('work-items-content');
218
- (wiTable || document).querySelectorAll('tr').forEach(function(r) { if (r.textContent.includes(id)) r.remove(); });
221
+ _removeWiRow(id);
219
222
  try {
220
223
  const res = await fetch('/api/work-items/cancel', {
221
224
  method: 'POST', headers: { 'Content-Type': 'application/json' },
@@ -228,8 +231,7 @@ async function cancelWorkItem(id, source) {
228
231
  async function archiveWorkItem(id, source) {
229
232
  showToast('cmd-toast', 'Archived ' + id, true);
230
233
  markDeleted('wi:' + id);
231
- var wiTable = document.getElementById('work-items-content');
232
- (wiTable || document).querySelectorAll('tr').forEach(function(r) { if (r.textContent.includes(id)) r.remove(); });
234
+ _removeWiRow(id);
233
235
  try {
234
236
  const res = await fetch('/api/work-items/archive', {
235
237
  method: 'POST', headers: { 'Content-Type': 'application/json' },
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-02T05:19:51.665Z"
4
+ "cachedAt": "2026-05-02T05:22:33.108Z"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1677",
3
+ "version": "0.1.1678",
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"