@yemi33/minions 0.1.109 → 0.1.111

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.111 (2026-04-01)
4
+
5
+ ### Dashboard
6
+ - dashboard.js
7
+
8
+ ## 0.1.110 (2026-04-01)
9
+
10
+ ### Dashboard
11
+ - dashboard/js/render-schedules.js
12
+
3
13
  ## 0.1.109 (2026-04-01)
4
14
 
5
15
  ### Dashboard
@@ -177,23 +177,33 @@ async function _parseNaturalCron() {
177
177
 
178
178
  // ─── Rendering ──────────────────────────────────────────────────────────────
179
179
 
180
+ let _schedPage = 0;
181
+ const SCHED_PER_PAGE = 15;
182
+
180
183
  function renderSchedules(schedules) {
181
184
  const el = document.getElementById('scheduled-content');
182
185
  const countEl = document.getElementById('scheduled-count');
183
186
  countEl.textContent = schedules.length;
187
+ window._lastSchedules = schedules;
184
188
  if (!schedules.length) {
185
189
  el.innerHTML = '<p class="empty">No scheduled tasks. Add one to automate recurring work.</p>';
186
190
  return;
187
191
  }
192
+
193
+ const totalPages = Math.ceil(schedules.length / SCHED_PER_PAGE);
194
+ if (_schedPage >= totalPages) _schedPage = totalPages - 1;
195
+ const start = _schedPage * SCHED_PER_PAGE;
196
+ const pageItems = schedules.slice(start, start + SCHED_PER_PAGE);
197
+
188
198
  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>';
189
- for (const s of schedules) {
199
+ for (const s of pageItems) {
190
200
  const enabledBadge = s.enabled
191
201
  ? '<span class="pr-badge approved">enabled</span>'
192
202
  : '<span class="pr-badge rejected">disabled</span>';
193
203
  const lastRun = s._lastRun ? timeAgo(s._lastRun) : 'never';
194
204
  const typeBadge = '<span class="dispatch-type ' + escHtml(s.type || 'implement') + '">' + escHtml(s.type || 'implement') + '</span>';
195
205
  const humanCron = _cronToHuman(s.cron || '');
196
- html += '<tr>' +
206
+ html += '<tr style="cursor:pointer" onclick="openScheduleDetail(\'' + escHtml(s.id) + '\')">' +
197
207
  '<td><span class="pr-id">' + escHtml(s.id || '') + '</span></td>' +
198
208
  '<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml(s.title || '') + '">' + escHtml(s.title || '') + '</td>' +
199
209
  '<td><span title="' + escHtml(s.cron || '') + '" style="font-size:11px;color:var(--blue)">' + escHtml(humanCron) + '</span></td>' +
@@ -210,8 +220,53 @@ function renderSchedules(schedules) {
210
220
  '</tr>';
211
221
  }
212
222
  html += '</tbody></table></div>';
223
+
224
+ if (schedules.length > SCHED_PER_PAGE) {
225
+ html += '<div class="pr-pager">' +
226
+ '<span class="pr-page-info">Showing ' + (start+1) + ' to ' + Math.min(start+SCHED_PER_PAGE, schedules.length) + ' of ' + schedules.length + '</span>' +
227
+ '<div class="pr-pager-btns">' +
228
+ '<button class="pr-pager-btn ' + (_schedPage === 0 ? 'disabled' : '') + '" onclick="_schedPrev()">Prev</button>' +
229
+ '<button class="pr-pager-btn ' + (_schedPage >= totalPages-1 ? 'disabled' : '') + '" onclick="_schedNext()">Next</button>' +
230
+ '</div>' +
231
+ '</div>';
232
+ }
233
+
213
234
  el.innerHTML = html;
214
- window._lastSchedules = schedules;
235
+ }
236
+
237
+ function _schedPrev() { if (_schedPage > 0) { _schedPage--; renderSchedules(window._lastSchedules || []); } }
238
+ function _schedNext() { var tp = Math.ceil((window._lastSchedules || []).length / SCHED_PER_PAGE); if (_schedPage < tp-1) { _schedPage++; renderSchedules(window._lastSchedules || []); } }
239
+
240
+ function openScheduleDetail(id) {
241
+ const s = (window._lastSchedules || []).find(x => x.id === id);
242
+ if (!s) return;
243
+ const humanCron = _cronToHuman(s.cron || '');
244
+ const lastRun = s._lastRun ? new Date(s._lastRun).toLocaleString() : 'never';
245
+ const enabledLabel = s.enabled ? '<span class="pr-badge approved">enabled</span>' : '<span class="pr-badge rejected">disabled</span>';
246
+
247
+ document.getElementById('modal-title').innerHTML = escHtml(s.title || s.id) +
248
+ ' <div style="display:flex;gap:4px;margin-top:4px">' +
249
+ '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--blue)" onclick="closeModal();openEditScheduleModal(\'' + escHtml(s.id) + '\')">Edit</button>' +
250
+ '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:' + (s.enabled ? 'var(--yellow)' : 'var(--green)') + '" onclick="toggleScheduleEnabled(\'' + escHtml(s.id) + '\',' + !s.enabled + ');closeModal()">' + (s.enabled ? 'Disable' : 'Enable') + '</button>' +
251
+ '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--red)" onclick="deleteSchedule(\'' + escHtml(s.id) + '\');closeModal()">Delete</button>' +
252
+ '</div>';
253
+
254
+ var body = '<div style="display:flex;flex-direction:column;gap:10px;font-size:12px;line-height:1.6">' +
255
+ '<div><strong style="color:var(--muted)">ID:</strong> ' + escHtml(s.id) + '</div>' +
256
+ '<div><strong style="color:var(--muted)">Schedule:</strong> <span style="color:var(--blue)">' + escHtml(humanCron) + '</span> <code style="background:var(--bg);padding:1px 4px;border-radius:3px;font-size:10px">' + escHtml(s.cron || '') + '</code></div>' +
257
+ '<div><strong style="color:var(--muted)">Type:</strong> <span class="dispatch-type ' + escHtml(s.type || 'implement') + '">' + escHtml(s.type || 'implement') + '</span></div>' +
258
+ '<div><strong style="color:var(--muted)">Priority:</strong> ' + escHtml(s.priority || 'medium') + '</div>' +
259
+ '<div><strong style="color:var(--muted)">Project:</strong> ' + escHtml(s.project || 'any') + '</div>' +
260
+ '<div><strong style="color:var(--muted)">Agent:</strong> ' + escHtml(s.agent || 'auto') + '</div>' +
261
+ '<div><strong style="color:var(--muted)">Status:</strong> ' + enabledLabel + '</div>' +
262
+ '<div><strong style="color:var(--muted)">Last Run:</strong> ' + escHtml(lastRun) + '</div>' +
263
+ (s.description ? '<div><strong style="color:var(--muted)">Description:</strong><div style="margin-top:4px;padding:8px;background:var(--surface2);border-radius:4px">' + renderMd(s.description) + '</div></div>' : '') +
264
+ '</div>';
265
+
266
+ document.getElementById('modal-body').innerHTML = body;
267
+ document.getElementById('modal-body').style.whiteSpace = 'normal';
268
+ document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
269
+ document.getElementById('modal').classList.add('open');
215
270
  }
216
271
 
217
272
  // ─── Form ───────────────────────────────────────────────────────────────────
@@ -405,4 +460,4 @@ async function deleteSchedule(id) {
405
460
  // Expose _generateScheduleId globally for the inline oninput handler
406
461
  window._generateScheduleId = _generateScheduleId;
407
462
 
408
- window.MinionsSchedules = { renderSchedules, openCreateScheduleModal, openEditScheduleModal, submitSchedule, toggleScheduleEnabled, deleteSchedule, _cronToHuman, _parseNaturalCron, _toggleCronMode, _quickSelectDays, _toggleDayPill, _updateCronPreview };
463
+ window.MinionsSchedules = { renderSchedules, openCreateScheduleModal, openEditScheduleModal, openScheduleDetail, submitSchedule, toggleScheduleEnabled, deleteSchedule, _cronToHuman, _parseNaturalCron, _toggleCronMode, _quickSelectDays, _toggleDayPill, _updateCronPreview, _schedPrev, _schedNext };
package/dashboard.js CHANGED
@@ -1453,55 +1453,53 @@ const server = http.createServer(async (req, res) => {
1453
1453
  manifest.push({ category: e.cat, file: e.file, title: e.title, agent: e.agent, date: e.date, content: content.slice(0, 3000) });
1454
1454
  }
1455
1455
 
1456
- const prompt = `You are a knowledge base curator. Analyze these ${manifest.length} knowledge base entries and produce a cleanup plan.
1456
+ const { callLLM, trackEngineUsage } = require('./engine/llm');
1457
+ const BATCH_SIZE = 80; // ~80 entries per batch to stay within Haiku context
1458
+ const batches = [];
1459
+ for (let i = 0; i < manifest.length; i += BATCH_SIZE) {
1460
+ batches.push(manifest.slice(i, i + BATCH_SIZE));
1461
+ }
1462
+
1463
+ const plan = { duplicates: [], reclassify: [], remove: [] };
1464
+ for (let b = 0; b < batches.length; b++) {
1465
+ const batch = batches[b];
1466
+ const offset = b * BATCH_SIZE;
1467
+ const prompt = `You are a knowledge base curator. Analyze these ${batch.length} entries (batch ${b + 1}/${batches.length}, indices ${offset}-${offset + batch.length - 1}) and produce a cleanup plan.
1457
1468
 
1458
1469
  ## Entries
1459
1470
 
1460
- ${manifest.map((m, i) => `<entry index="${i}" category="${m.category}" file="${m.file}" date="${m.date}" agent="${m.agent || 'unknown'}">
1471
+ ${batch.map((m, i) => `<entry index="${offset + i}" category="${m.category}" file="${m.file}" date="${m.date}" agent="${m.agent || 'unknown'}">
1461
1472
  ${m.title}
1462
- ${m.content.slice(0, 1500)}
1473
+ ${m.content.slice(0, 800)}
1463
1474
  </entry>`).join('\n\n')}
1464
1475
 
1465
1476
  ## Instructions
1466
1477
 
1467
- 1. **Find duplicates**: entries with substantially the same content or insights (same findings from different agents or dispatch runs). List pairs/groups by index. When choosing which to keep, prefer the more recent entry (later date) as it likely reflects the current state of the codebase.
1468
-
1469
- 2. **Find misclassified**: entries in the wrong category. Common: build reports in conventions, reviews in architecture.
1478
+ 1. **Find duplicates**: entries with substantially the same content (same findings, different agents/runs). List pairs by index. Prefer keeping the more recent entry.
1479
+ 2. **Find misclassified**: entries in the wrong category.
1480
+ 3. **Find stale/empty**: entries with no actionable content (boilerplate, bail-out notes, "no changes needed").
1470
1481
 
1471
- 3. **Find stale/empty**: entries with no actionable content (boilerplate, "no changes needed", bail-out notes).
1472
-
1473
- ## Output Format
1474
-
1475
- Respond with ONLY valid JSON (no markdown fences, no preamble):
1476
-
1477
- {
1478
- "duplicates": [
1479
- { "keep": 0, "remove": [1, 5], "reason": "same PR review findings" }
1480
- ],
1481
- "reclassify": [
1482
- { "index": 3, "from": "conventions", "to": "build-reports", "reason": "..." }
1483
- ],
1484
- "remove": [
1485
- { "index": 7, "reason": "empty bail-out note" }
1486
- ]
1487
- }
1482
+ Respond with ONLY valid JSON: { "duplicates": [{ "keep": N, "remove": [N], "reason": "..." }], "reclassify": [{ "index": N, "from": "cat", "to": "cat", "reason": "..." }], "remove": [{ "index": N, "reason": "..." }] }
1483
+ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
1488
1484
 
1489
- If nothing to do, return: { "duplicates": [], "reclassify": [], "remove": [] }`;
1490
-
1491
- const { callLLM, trackEngineUsage } = require('./engine/llm');
1492
- const result = await callLLM(prompt, 'You are a concise knowledge curator. Output only JSON.', {
1493
- timeout: 180000, label: 'kb-sweep', model: 'haiku', maxTurns: 1
1494
- });
1495
- trackEngineUsage('kb-sweep', result.usage);
1485
+ const result = await callLLM(prompt, 'Output only JSON.', {
1486
+ timeout: 120000, label: 'kb-sweep', model: 'haiku', maxTurns: 1
1487
+ });
1488
+ trackEngineUsage('kb-sweep', result.usage);
1496
1489
 
1497
- let plan;
1498
- try {
1499
- let jsonStr = result.text.trim();
1500
- const fenceMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
1501
- if (fenceMatch) jsonStr = fenceMatch[1].trim();
1502
- plan = JSON.parse(jsonStr);
1503
- } catch {
1504
- return jsonReply(res, 200, { ok: false, error: 'LLM returned invalid JSON', raw: result.text.slice(0, 500) });
1490
+ let batchPlan;
1491
+ try {
1492
+ let jsonStr = (result.text || '').trim();
1493
+ const fenceMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
1494
+ if (fenceMatch) jsonStr = fenceMatch[1].trim();
1495
+ batchPlan = JSON.parse(jsonStr);
1496
+ } catch {
1497
+ console.log(`[kb-sweep] batch ${b + 1}/${batches.length} returned invalid JSON, skipping`);
1498
+ continue;
1499
+ }
1500
+ if (batchPlan.duplicates) plan.duplicates.push(...batchPlan.duplicates);
1501
+ if (batchPlan.reclassify) plan.reclassify.push(...batchPlan.reclassify);
1502
+ if (batchPlan.remove) plan.remove.push(...batchPlan.remove);
1505
1503
  }
1506
1504
 
1507
1505
  let removed = 0, reclassified = 0, merged = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.109",
3
+ "version": "0.1.111",
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"