@yemi33/minions 0.1.278 → 0.1.280

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,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.280 (2026-04-03)
4
+
5
+ ### Features
6
+ - thinking indicator shows progressive phases with elapsed timer
7
+
8
+ ### Fixes
9
+ - doc chat elapsed time no longer overlaps copy button
10
+
3
11
  ## 0.1.278 (2026-04-03)
4
12
 
5
13
  ### Features
@@ -215,6 +215,15 @@ async function _ccDoSend(message, skipUserMsg) {
215
215
  let streamedText = '';
216
216
  let toolsUsed = [];
217
217
  const dotPulse = '<span style="display:inline-flex;gap:3px;margin-left:6px;vertical-align:middle"><span style="width:4px;height:4px;background:var(--blue);border-radius:50%;animation:dotPulse 1.2s infinite"></span><span style="width:4px;height:4px;background:var(--blue);border-radius:50%;animation:dotPulse 1.2s infinite;animation-delay:0.2s"></span><span style="width:4px;height:4px;background:var(--blue);border-radius:50%;animation:dotPulse 1.2s infinite;animation-delay:0.4s"></span></span>';
218
+ function _getThinkingPhase() {
219
+ var elapsed = Date.now() - ccStartTime;
220
+ var label = 'Thinking...';
221
+ for (var pi = phases.length - 1; pi >= 0; pi--) {
222
+ if (elapsed >= phases[pi][0]) { label = phases[pi][1]; break; }
223
+ }
224
+ var secs = Math.floor(elapsed / 1000);
225
+ return label + ' <span style="font-size:9px;color:var(--border)">' + secs + 's</span>';
226
+ }
218
227
  function updateStreamDiv() {
219
228
  var html = '';
220
229
  if (toolsUsed.length > 0) {
@@ -227,13 +236,16 @@ async function _ccDoSend(message, skipUserMsg) {
227
236
  if (streamedText) {
228
237
  html += renderMd(streamedText);
229
238
  }
230
- html += '<div style="margin-top:' + (streamedText ? '6px' : '0') + '"><span style="color:var(--muted);font-size:11px">Thinking...' + dotPulse + '</span></div>';
239
+ html += '<div style="margin-top:' + (streamedText ? '6px' : '0') + '"><span style="color:var(--muted);font-size:11px">' + _getThinkingPhase() + dotPulse + '</span></div>';
231
240
  streamDiv.innerHTML = html;
232
241
  // Re-append queue indicators so they stay below the streaming content
233
242
  if (_ccQueue.length > 0) _renderQueueIndicator();
234
243
  if (msgs.scrollHeight - msgs.scrollTop - msgs.clientHeight < 150) msgs.scrollTop = msgs.scrollHeight;
235
244
  }
236
245
 
246
+ // Periodically update thinking phase text + timer
247
+ const phaseTimer = setInterval(updateStreamDiv, 1000);
248
+
237
249
  const reader = res.body.getReader();
238
250
  const decoder = new TextDecoder();
239
251
  let buf = '';
@@ -257,7 +269,7 @@ async function _ccDoSend(message, skipUserMsg) {
257
269
  if (msgs.scrollHeight - msgs.scrollTop - msgs.clientHeight < 150) msgs.scrollTop = msgs.scrollHeight;
258
270
  } else if (evt.type === 'done') {
259
271
  // Replace streaming div with a proper ccAddMessage
260
- streamDiv.remove();
272
+ clearInterval(phaseTimer); streamDiv.remove();
261
273
  // placeholder was added with skipSave=true — nothing to pop
262
274
  const ccElapsed = Math.round((Date.now() - ccStartTime) / 1000);
263
275
  const rendered = renderMd(evt.text || streamedText || '');
@@ -267,7 +279,7 @@ async function _ccDoSend(message, skipUserMsg) {
267
279
  for (const action of evt.actions) { await ccExecuteAction(action); }
268
280
  }
269
281
  } else if (evt.type === 'error') {
270
- streamDiv.remove();
282
+ clearInterval(phaseTimer); streamDiv.remove();
271
283
  // placeholder was skipSave — no pop needed
272
284
  ccAddMessage('assistant', '<span style="color:var(--red)">' + escHtml(evt.error) + '</span>');
273
285
  }
@@ -281,7 +293,7 @@ async function _ccDoSend(message, skipUserMsg) {
281
293
  try {
282
294
  const evt = JSON.parse(line.slice(6));
283
295
  if (evt.type === 'done') {
284
- streamDiv.remove();
296
+ clearInterval(phaseTimer); streamDiv.remove();
285
297
  // placeholder was skipSave — no pop needed
286
298
  const ccElapsed = Math.round((Date.now() - ccStartTime) / 1000);
287
299
  const rendered = renderMd(evt.text || streamedText || '');
@@ -299,7 +311,7 @@ async function _ccDoSend(message, skipUserMsg) {
299
311
  }
300
312
  // If stream ended without a 'done' event, finalize with whatever we have
301
313
  if (streamDiv.parentNode) {
302
- streamDiv.remove();
314
+ clearInterval(phaseTimer); streamDiv.remove();
303
315
  if (streamedText) {
304
316
  const ccElapsed = Math.round((Date.now() - ccStartTime) / 1000);
305
317
  ccAddMessage('assistant', renderMd(streamedText) + '<div style="font-size:9px;color:var(--muted);margin-top:6px;display:flex;justify-content:flex-end;padding-right:30px">' + ccElapsed + 's</div>');
@@ -314,6 +326,7 @@ async function _ccDoSend(message, skipUserMsg) {
314
326
  } finally {
315
327
  _ccSending = false;
316
328
  _ccAbortController = null;
329
+ try { clearInterval(phaseTimer); } catch { /* may not be defined if error before reader */ }
317
330
  try { localStorage.removeItem('cc-sending'); } catch {}
318
331
  // Show notification badge on CC button if drawer is closed
319
332
  if (!_ccOpen) showNotifBadge(document.getElementById('cc-toggle-btn'));
@@ -6,8 +6,22 @@ let _modalFilePath = null; // file path for steering (null = read-only Q&A only)
6
6
  function showModalQa() {
7
7
  document.getElementById('modal-qa').style.display = '';
8
8
  }
9
+
10
+ function _qaNotifySidebar(filePath) {
11
+ if (!filePath) return;
12
+ let page = '';
13
+ if (/^plans\//.test(filePath) || /^prd\//.test(filePath)) page = 'plans';
14
+ else if (/^knowledge\//.test(filePath) || /^notes/.test(filePath) || /^pinned/.test(filePath)) page = 'inbox';
15
+ else if (/^meetings\//.test(filePath)) page = 'inbox';
16
+ else return;
17
+ // Don't badge if user is already on that page
18
+ const currentPage = document.querySelector('.sidebar-link.active')?.getAttribute('data-page');
19
+ if (currentPage === page) return;
20
+ const link = document.querySelector('.sidebar-link[data-page="' + page + '"]');
21
+ if (link && !link.querySelector('.notif-badge')) showNotifBadge(link);
22
+ }
9
23
  let _qaHistory = []; // multi-turn conversation history [{role:'user',text:''},{role:'assistant',text:''}]
10
- let _qaProcessing = false; // true while waiting for Haiku response
24
+ let _qaProcessing = false; // true while waiting for response
11
25
  let _qaQueue = []; // queued messages while processing
12
26
  let _qaSessionKey = ''; // key for current conversation (title or filePath)
13
27
  const _qaSessions = new Map(); // persist conversations across modal open/close {key → {history, threadHtml}}
@@ -199,13 +213,16 @@ async function _processQaMessage(message, selection) {
199
213
  const borderColor = data.edited ? 'var(--green)' : 'var(--blue)';
200
214
  const suffix = data.edited ? '\n\n\u2713 Document saved.' : '';
201
215
  const qaElapsed = Math.round((Date.now() - qaStartTime) / 1000);
202
- const qaTimeLabel = '<div style="font-size:9px;color:var(--muted);margin-top:4px;text-align:right">' + qaElapsed + 's</div>';
216
+ const qaTimeLabel = '<div style="font-size:9px;color:var(--muted);margin-top:4px;text-align:right;padding-right:24px">' + qaElapsed + 's</div>';
203
217
  thread.innerHTML += '<div class="modal-qa-a" style="border-left-color:' + borderColor + '">' + llmCopyBtn() + renderMd(data.answer + suffix) + qaTimeLabel + '</div>';
204
218
 
205
219
  // Track conversation history
206
220
  _qaHistory.push({ role: 'user', text: message });
207
221
  _qaHistory.push({ role: 'assistant', text: data.answer });
208
222
 
223
+ // Notify sidebar page link
224
+ _qaNotifySidebar(capturedFilePath);
225
+
209
226
  // Execute any CC actions (dispatch, note, etc.)
210
227
  if (data.actions && data.actions.length > 0) {
211
228
  for (const action of data.actions) { await ccExecuteAction(action); }
@@ -267,7 +284,7 @@ async function _processQaMessage(message, selection) {
267
284
  filePath: sessionFilePath,
268
285
  });
269
286
  _saveQaSessions();
270
- // If modal was closed while processing, show notification badge on source card
287
+ // Show notification badge on source card when modal was closed during processing
271
288
  if (!modalIsOpen) {
272
289
  const card = findCardForFile(sessionFilePath);
273
290
  if (card) showNotifBadge(card);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.278",
3
+ "version": "0.1.280",
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"