@yemi33/minions 0.1.901 → 0.1.903

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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.903 (2026-04-12)
4
+
5
+ ### Fixes
6
+ - make KB sweep endpoint async with status polling (#933)
7
+ - prohibit agents from self-merging their own PRs
8
+
3
9
  ## 0.1.901 (2026-04-12)
4
10
 
5
11
  ### Fixes
@@ -144,21 +144,49 @@ async function kbSweep() {
144
144
  btn.style.color = 'var(--blue)';
145
145
  try {
146
146
  showToast('cmd-toast', 'KB sweep started', true);
147
- const res = await fetch('/api/knowledge/sweep', { method: 'POST', signal: AbortSignal.timeout(300000), headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ pinnedKeys: getPinnedItems().filter(function(k) { return k.startsWith('knowledge/'); }) }) });
148
- const data = await res.json();
149
- if (data.ok) {
150
- btn.textContent = 'done';
151
- btn.style.color = 'var(--green)';
152
- refreshKnowledgeBase();
153
- } else {
147
+ const triggerRes = await fetch('/api/knowledge/sweep', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ pinnedKeys: getPinnedItems().filter(function(k) { return k.startsWith('knowledge/'); }) }) });
148
+ const triggerData = await triggerRes.json();
149
+ if (!triggerData.ok) {
154
150
  btn.style.color = 'var(--red)';
155
151
  btn.textContent = 'failed';
156
- showToast('cmd-toast', 'Sweep failed: ' + (data.error || 'unknown'), false);
152
+ showToast('cmd-toast', 'Sweep failed: ' + (triggerData.error || 'unknown'), false);
153
+ setTimeout(function() { btn.textContent = origText; btn.style.color = 'var(--muted)'; btn.disabled = false; }, 60000);
154
+ return;
155
+ }
156
+ // Poll status until sweep completes (every 3s, up to 10 min)
157
+ var maxPolls = 200;
158
+ var pollCount = 0;
159
+ while (pollCount < maxPolls) {
160
+ await new Promise(function(r) { setTimeout(r, 3000); });
161
+ pollCount++;
162
+ try {
163
+ var statusRes = await fetch('/api/knowledge/sweep/status');
164
+ var statusData = await statusRes.json();
165
+ if (!statusData.inFlight) {
166
+ var result = statusData.lastResult;
167
+ if (result && result.ok) {
168
+ btn.textContent = 'done';
169
+ btn.style.color = 'var(--green)';
170
+ showToast('cmd-toast', 'KB sweep complete: ' + (result.summary || 'done'), true);
171
+ refreshKnowledgeBase();
172
+ } else {
173
+ btn.style.color = 'var(--red)';
174
+ btn.textContent = 'failed';
175
+ showToast('cmd-toast', 'Sweep failed: ' + ((result && result.error) || 'unknown'), false);
176
+ }
177
+ break;
178
+ }
179
+ } catch { /* poll error — retry */ }
180
+ }
181
+ if (pollCount >= maxPolls) {
182
+ btn.textContent = 'timeout';
183
+ btn.style.color = 'var(--red)';
184
+ showToast('cmd-toast', 'Sweep polling timed out — check status later', false);
157
185
  }
158
186
  // Show notification on sidebar if user is on a different page
159
187
  var kbLink = document.querySelector('.sidebar-link[data-page="inbox"]');
160
188
  var activePage = document.querySelector('.sidebar-link.active')?.getAttribute('data-page');
161
- if (kbLink && activePage !== 'inbox') showNotifBadge(kbLink, data.ok ? 'done' : 'done');
189
+ if (kbLink && activePage !== 'inbox') showNotifBadge(kbLink, 'done');
162
190
  } catch (e) {
163
191
  btn.style.color = 'var(--red)';
164
192
  btn.textContent = 'failed';
@@ -167,8 +195,8 @@ async function kbSweep() {
167
195
  var activePage2 = document.querySelector('.sidebar-link.active')?.getAttribute('data-page');
168
196
  if (kbLink2 && activePage2 !== 'inbox') showNotifBadge(kbLink2);
169
197
  }
170
- const isError = btn.textContent === 'failed';
171
- setTimeout(() => { btn.textContent = origText; btn.style.color = 'var(--muted)'; btn.disabled = false; }, isError ? 60000 : 3000);
198
+ var isError = btn.textContent === 'failed' || btn.textContent === 'timeout';
199
+ setTimeout(function() { btn.textContent = origText; btn.style.color = 'var(--muted)'; btn.disabled = false; }, isError ? 60000 : 3000);
172
200
  }
173
201
 
174
202
  function openCreateKbModal() {
package/dashboard.js CHANGED
@@ -1942,16 +1942,28 @@ const server = http.createServer(async (req, res) => {
1942
1942
  console.log('[kb-sweep] Auto-releasing stale guard (>5min)');
1943
1943
  global._kbSweepInFlight = false;
1944
1944
  }
1945
- if (global._kbSweepInFlight) return jsonReply(res, 409, { error: 'sweep already in progress' });
1945
+ if (global._kbSweepInFlight) {
1946
+ return jsonReply(res, 200, { ok: true, alreadyRunning: true, startedAt: global._kbSweepStartedAt });
1947
+ }
1946
1948
  // Generation token prevents stale finally blocks from clearing the flag for a new sweep
1947
1949
  const sweepToken = Date.now() + Math.random();
1948
1950
  global._kbSweepToken = sweepToken;
1949
1951
  global._kbSweepInFlight = true;
1950
1952
  global._kbSweepStartedAt = Date.now();
1953
+ const body = await readBody(req).catch(() => ({}));
1954
+ // Run sweep in background — return immediately so agents/UI don't time out
1955
+ _runKbSweepBackground(body, sweepToken);
1956
+ return jsonReply(res, 202, { ok: true, started: true });
1957
+ }
1958
+
1959
+ async function _runKbSweepBackground(body, sweepToken) {
1951
1960
  try {
1952
- const body = await readBody(req).catch(() => ({}));
1953
1961
  const entries = getKnowledgeBaseEntries();
1954
- if (entries.length < 2) return jsonReply(res, 200, { ok: true, summary: 'nothing to sweep (< 2 entries)' });
1962
+ if (entries.length < 2) {
1963
+ global._kbSweepLastResult = { ok: true, summary: 'nothing to sweep (< 2 entries)' };
1964
+ global._kbSweepLastCompletedAt = Date.now();
1965
+ return;
1966
+ }
1955
1967
 
1956
1968
  // Build a manifest of all KB entries with their content (skip pinned — user wants to keep them)
1957
1969
  const pinnedKeys = new Set(body.pinnedKeys || []);
@@ -2012,10 +2024,12 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
2012
2024
  let removed = 0, reclassified = 0, merged = 0;
2013
2025
  const kbDir = path.join(MINIONS_DIR, 'knowledge');
2014
2026
 
2015
- // If nothing to do, return early
2027
+ // If nothing to do, store result and return
2016
2028
  const totalActions = (plan.remove || []).length + (plan.duplicates || []).reduce((n, d) => n + (d.remove || []).length, 0) + (plan.reclassify || []).length;
2017
2029
  if (totalActions === 0) {
2018
- return jsonReply(res, 200, { ok: true, summary: 'KB is clean — nothing to sweep', plan });
2030
+ global._kbSweepLastResult = { ok: true, summary: 'KB is clean — nothing to sweep', plan };
2031
+ global._kbSweepLastCompletedAt = Date.now();
2032
+ return;
2019
2033
  }
2020
2034
 
2021
2035
  // Archive dir for swept files (never delete, always preserve)
@@ -2086,8 +2100,22 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
2086
2100
 
2087
2101
  const summary = `${merged} duplicates merged, ${removed} stale removed, ${reclassified} reclassified${pruned ? ', ' + pruned + ' old swept files pruned' : ''}`;
2088
2102
  safeWrite(path.join(ENGINE_DIR, 'kb-swept.json'), JSON.stringify({ timestamp: new Date().toISOString(), summary }));
2089
- return jsonReply(res, 200, { ok: true, summary, plan });
2090
- } catch (e) { return jsonReply(res, 500, { error: e.message }); } finally { if (global._kbSweepToken === sweepToken) global._kbSweepInFlight = false; }
2103
+ global._kbSweepLastResult = { ok: true, summary, plan };
2104
+ global._kbSweepLastCompletedAt = Date.now();
2105
+ } catch (e) {
2106
+ console.error('[kb-sweep] background error:', e.message);
2107
+ global._kbSweepLastResult = { ok: false, error: e.message };
2108
+ global._kbSweepLastCompletedAt = Date.now();
2109
+ } finally { if (global._kbSweepToken === sweepToken) global._kbSweepInFlight = false; }
2110
+ }
2111
+
2112
+ function handleKnowledgeSweepStatus(req, res) {
2113
+ return jsonReply(res, 200, {
2114
+ inFlight: !!global._kbSweepInFlight,
2115
+ startedAt: global._kbSweepStartedAt || null,
2116
+ lastResult: global._kbSweepLastResult || null,
2117
+ lastCompletedAt: global._kbSweepLastCompletedAt || null
2118
+ });
2091
2119
  }
2092
2120
 
2093
2121
  async function handlePlansList(req, res) {
@@ -4131,7 +4159,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
4131
4159
  invalidateStatusCache();
4132
4160
  return jsonReply(res, 200, { ok: true, path: filePath });
4133
4161
  }},
4134
- { method: 'POST', path: '/api/knowledge/sweep', desc: 'Deduplicate, consolidate, and reorganize knowledge base', handler: handleKnowledgeSweep },
4162
+ { method: 'POST', path: '/api/knowledge/sweep', desc: 'Trigger async KB sweep (returns 202)', handler: handleKnowledgeSweep },
4163
+ { method: 'GET', path: '/api/knowledge/sweep/status', desc: 'Poll KB sweep status', handler: handleKnowledgeSweepStatus },
4135
4164
  { method: 'GET', path: /^\/api\/knowledge\/([^/]+)\/([^?]+)/, desc: 'Read a specific knowledge base entry', handler: handleKnowledgeRead },
4136
4165
 
4137
4166
  // Doc chat
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.901",
3
+ "version": "0.1.903",
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"
package/playbooks/fix.md CHANGED
@@ -76,6 +76,8 @@ After pushing, respond to each review comment/thread:
76
76
 
77
77
  Your task is complete once you have: (1) confirmed build and tests pass, (2) pushed the fix, (3) commented on the PR, and (4) resolved addressed review threads. Do NOT continue exploring unrelated code or making additional improvements. Stop immediately.
78
78
 
79
+ **NEVER run `gh pr merge` or any merge command on this PR.** The engine handles merging after review approval. Self-merging bypasses the review cycle and is prohibited.
80
+
79
81
  ## Completion
80
82
 
81
83
  After finishing, output a structured completion block so the engine can parse your results:
@@ -82,3 +82,5 @@ Include build/test status and run instructions in the PR description. If the pro
82
82
  ## When to Stop
83
83
 
84
84
  Your task is complete once you have: (1) confirmed build and tests pass, (2) pushed your branch, and (3) created the PR. Your final message MUST include the PR URL so the engine can track it. Stop immediately after.
85
+
86
+ **NEVER run `gh pr merge` or any merge command on your own PR.** The engine reviews and merges PRs through a separate review cycle. Self-merging bypasses code review entirely and is prohibited regardless of circumstances.
@@ -58,3 +58,5 @@ If you encounter merge conflicts during push or PR creation:
58
58
  ## When to Stop
59
59
 
60
60
  Your task is complete once you have: (1) confirmed build and tests pass, (2) pushed your branch, and (3) created the PR. Do NOT continue beyond the task description. Stop immediately.
61
+
62
+ **NEVER run `gh pr merge` or any merge command on your own PR.** The engine reviews and merges PRs through a separate review cycle. Self-merging bypasses code review entirely and is prohibited regardless of circumstances.