@yemi33/minions 0.1.2175 → 0.1.2176
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.
|
@@ -29,6 +29,12 @@
|
|
|
29
29
|
var messages = [];
|
|
30
30
|
var sending = false;
|
|
31
31
|
var abortController = null;
|
|
32
|
+
// Per-tab queue of follow-up messages submitted while a turn is streaming
|
|
33
|
+
// (W-mqayw3x6). Keyed by tabId so each chat tab keeps its own queue across
|
|
34
|
+
// tab switches. In-memory only — a hard refresh drops the queue by design.
|
|
35
|
+
var queues = {};
|
|
36
|
+
var queueSuspended = {}; // tabId -> true after an abort/error suspends auto-drain
|
|
37
|
+
var queueEl = null; // bottom-pinned container that holds the queued bubbles
|
|
32
38
|
var currentProject = null;
|
|
33
39
|
try { currentProject = localStorage.getItem(SLIM_PROJECT_KEY) || null; } catch (_e) { /* private mode */ }
|
|
34
40
|
|
|
@@ -241,7 +247,7 @@
|
|
|
241
247
|
} else {
|
|
242
248
|
div.textContent = text;
|
|
243
249
|
}
|
|
244
|
-
|
|
250
|
+
_appendMsgEl(div);
|
|
245
251
|
scrollToBottom();
|
|
246
252
|
return div;
|
|
247
253
|
}
|
|
@@ -249,11 +255,85 @@
|
|
|
249
255
|
var div = document.createElement('div');
|
|
250
256
|
div.className = 'chat-action ' + (severity || '');
|
|
251
257
|
div.textContent = label;
|
|
252
|
-
|
|
258
|
+
_appendMsgEl(div);
|
|
253
259
|
scrollToBottom();
|
|
254
260
|
return div;
|
|
255
261
|
}
|
|
256
262
|
|
|
263
|
+
// ── Queued-message rendering (W-mqayw3x6) ──────────────────────────
|
|
264
|
+
function _getQueue() {
|
|
265
|
+
return queues[tabId] || (queues[tabId] = []);
|
|
266
|
+
}
|
|
267
|
+
// Keep a single bottom-pinned container so queued bubbles always sit below
|
|
268
|
+
// the live transcript (newest content nearest the composer). appendChild on
|
|
269
|
+
// an already-attached node moves it, so this re-pins the container to the end.
|
|
270
|
+
function _ensureQueueEl() {
|
|
271
|
+
if (!queueEl || queueEl.parentNode !== msgsEl) {
|
|
272
|
+
queueEl = document.createElement('div');
|
|
273
|
+
queueEl.className = 'chat-queue';
|
|
274
|
+
}
|
|
275
|
+
msgsEl.appendChild(queueEl);
|
|
276
|
+
return queueEl;
|
|
277
|
+
}
|
|
278
|
+
// Insert a real/stream bubble above the queue container so the pending
|
|
279
|
+
// queued bubbles stay pinned at the bottom of the transcript.
|
|
280
|
+
function _appendMsgEl(el) {
|
|
281
|
+
if (queueEl && queueEl.parentNode === msgsEl) msgsEl.insertBefore(el, queueEl);
|
|
282
|
+
else msgsEl.appendChild(el);
|
|
283
|
+
}
|
|
284
|
+
// Rebuild the queued-message bubbles for the active tab: greyed-out user
|
|
285
|
+
// bubbles with a per-message dismiss. When auto-drain is suspended (after an
|
|
286
|
+
// abort/error) a "Send queued (N)" control lets the user resume explicitly.
|
|
287
|
+
function renderQueue() {
|
|
288
|
+
var q = _getQueue();
|
|
289
|
+
if (!q.length) {
|
|
290
|
+
if (queueEl && queueEl.parentNode) queueEl.parentNode.removeChild(queueEl);
|
|
291
|
+
queueEl = null;
|
|
292
|
+
queueSuspended[tabId] = false;
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
_ensureQueueEl();
|
|
296
|
+
queueEl.textContent = '';
|
|
297
|
+
if (queueSuspended[tabId]) {
|
|
298
|
+
var resume = document.createElement('button');
|
|
299
|
+
resume.className = 'chat-queue-resume';
|
|
300
|
+
resume.type = 'button';
|
|
301
|
+
resume.textContent = 'Send queued (' + q.length + ')';
|
|
302
|
+
resume.title = 'Send the queued messages now';
|
|
303
|
+
resume.addEventListener('click', function() {
|
|
304
|
+
queueSuspended[tabId] = false;
|
|
305
|
+
renderQueue();
|
|
306
|
+
_drainQueue();
|
|
307
|
+
});
|
|
308
|
+
queueEl.appendChild(resume);
|
|
309
|
+
}
|
|
310
|
+
q.forEach(function(text, i) {
|
|
311
|
+
var row = document.createElement('div');
|
|
312
|
+
row.className = 'chat-msg user queued';
|
|
313
|
+
var span = document.createElement('span');
|
|
314
|
+
span.className = 'chat-queue-text';
|
|
315
|
+
span.textContent = text;
|
|
316
|
+
row.appendChild(span);
|
|
317
|
+
var badge = document.createElement('span');
|
|
318
|
+
badge.className = 'chat-queue-badge';
|
|
319
|
+
badge.textContent = 'queued';
|
|
320
|
+
row.appendChild(badge);
|
|
321
|
+
var dismiss = document.createElement('button');
|
|
322
|
+
dismiss.className = 'chat-queue-dismiss';
|
|
323
|
+
dismiss.type = 'button';
|
|
324
|
+
dismiss.textContent = '×';
|
|
325
|
+
dismiss.title = 'Remove from queue';
|
|
326
|
+
dismiss.addEventListener('click', function() {
|
|
327
|
+
var qq = _getQueue();
|
|
328
|
+
qq.splice(i, 1);
|
|
329
|
+
renderQueue();
|
|
330
|
+
});
|
|
331
|
+
row.appendChild(dismiss);
|
|
332
|
+
queueEl.appendChild(row);
|
|
333
|
+
});
|
|
334
|
+
scrollToBottom();
|
|
335
|
+
}
|
|
336
|
+
|
|
257
337
|
// Render a tool invocation as a one-line string. `full` keeps the complete
|
|
258
338
|
// command/args (used by the tool-calls modal); otherwise long values are
|
|
259
339
|
// clipped for the collapsed 3-line panel.
|
|
@@ -370,7 +450,7 @@
|
|
|
370
450
|
tp.panel.style.display = 'none';
|
|
371
451
|
toolbar.appendChild(tp.panel);
|
|
372
452
|
|
|
373
|
-
|
|
453
|
+
_appendMsgEl(div);
|
|
374
454
|
scrollToBottom();
|
|
375
455
|
|
|
376
456
|
return {
|
|
@@ -411,26 +491,32 @@
|
|
|
411
491
|
|
|
412
492
|
function setSending(on) {
|
|
413
493
|
sending = on;
|
|
414
|
-
|
|
415
|
-
|
|
494
|
+
// Composer stays interactive while a turn streams so the user can keep
|
|
495
|
+
// typing and queue follow-up messages (W-mqayw3x6 #1). The Send button
|
|
496
|
+
// stays enabled — submitting mid-turn enqueues — and only its label
|
|
497
|
+
// reflects the in-flight state.
|
|
498
|
+
sendBtn.disabled = false;
|
|
499
|
+
sendBtn.textContent = on ? 'Queue' : 'Send';
|
|
416
500
|
stopBtn.style.display = on ? 'block' : 'none';
|
|
417
|
-
inputEl.disabled =
|
|
501
|
+
inputEl.disabled = false;
|
|
418
502
|
}
|
|
419
503
|
|
|
420
504
|
function rerenderHistory() {
|
|
421
505
|
msgsEl.innerHTML = '';
|
|
506
|
+
queueEl = null; // detached by the innerHTML reset; renderQueue rebuilds it
|
|
422
507
|
if (!messages.length) {
|
|
423
508
|
var empty = document.createElement('div');
|
|
424
509
|
empty.className = 'chat-empty';
|
|
425
510
|
empty.textContent = 'No messages yet — say hi to Command Center.';
|
|
426
511
|
msgsEl.appendChild(empty);
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
512
|
+
} else {
|
|
513
|
+
for (var i = 0; i < messages.length; i++) {
|
|
514
|
+
var m = messages[i];
|
|
515
|
+
if (m.role === 'action') appendActionStatus(m.severity || '', m.text || '');
|
|
516
|
+
else appendBubble(m.role, m.text || '', m.toolCalls);
|
|
517
|
+
}
|
|
433
518
|
}
|
|
519
|
+
renderQueue();
|
|
434
520
|
}
|
|
435
521
|
// ── Command Center tab bar (shared with the classic dashboard) ──
|
|
436
522
|
// One chip per shared cc-tabs entry, so the same conversations are visible
|
|
@@ -23,10 +23,60 @@
|
|
|
23
23
|
saveState();
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// Public entry point for the Send button / Enter key. While a turn is in
|
|
27
|
+
// flight the composer stays interactive (setSending no longer disables it),
|
|
28
|
+
// so a second submit QUEUES the message instead of dropping it or racing the
|
|
29
|
+
// active stream. The queue drains FIFO, one turn at a time (W-mqayw3x6).
|
|
30
|
+
function sendMessage() {
|
|
28
31
|
var text = inputEl.value.trim();
|
|
29
32
|
if (!text) return;
|
|
33
|
+
inputEl.value = '';
|
|
34
|
+
inputEl.style.height = 'auto';
|
|
35
|
+
if (sending) {
|
|
36
|
+
_enqueue(text);
|
|
37
|
+
inputEl.focus();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
_performSend(text);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Append to the active tab's pending queue and render the greyed bubbles.
|
|
44
|
+
function _enqueue(text) {
|
|
45
|
+
_getQueue().push(text);
|
|
46
|
+
renderQueue();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Promote the next queued message for the active tab, one at a time. No-op
|
|
50
|
+
// while a turn is in flight or the queue is suspended (after abort/error).
|
|
51
|
+
function _drainQueue() {
|
|
52
|
+
if (sending || queueSuspended[tabId]) return;
|
|
53
|
+
var q = _getQueue();
|
|
54
|
+
if (!q.length) return;
|
|
55
|
+
var next = q.shift();
|
|
56
|
+
renderQueue();
|
|
57
|
+
_performSend(next);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Called from a finished turn's `finally`. On clean completion auto-fire the
|
|
61
|
+
// next queued message; on abort or error suspend auto-drain and leave the
|
|
62
|
+
// queue visible so the user can resend or dismiss explicitly (W-mqayw3x6 #5/#6).
|
|
63
|
+
function _afterTurn(turnTabId, outcome) {
|
|
64
|
+
var q = queues[turnTabId];
|
|
65
|
+
if (!q || !q.length) return;
|
|
66
|
+
if (outcome === 'done' && turnTabId === tabId) {
|
|
67
|
+
_drainQueue();
|
|
68
|
+
} else {
|
|
69
|
+
queueSuspended[turnTabId] = true;
|
|
70
|
+
if (turnTabId === tabId) renderQueue();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function _performSend(text) {
|
|
75
|
+
// Bind this turn to the tab it started on so a mid-turn tab switch (which
|
|
76
|
+
// aborts the stream) settles the queue against the right tab, not the one
|
|
77
|
+
// the user navigated to.
|
|
78
|
+
var turnTabId = tabId;
|
|
79
|
+
var turnOutcome = 'done';
|
|
30
80
|
|
|
31
81
|
// The tab title is derived from the first user message; only that message
|
|
32
82
|
// changes the chip, so re-render the bar once instead of on every send.
|
|
@@ -36,8 +86,6 @@
|
|
|
36
86
|
saveState();
|
|
37
87
|
if (wasFirstUser) renderTabBar();
|
|
38
88
|
|
|
39
|
-
inputEl.value = '';
|
|
40
|
-
inputEl.style.height = 'auto';
|
|
41
89
|
setSending(true);
|
|
42
90
|
|
|
43
91
|
var stream = buildStreamBubble();
|
|
@@ -101,6 +149,7 @@
|
|
|
101
149
|
stream.replaceWithError(errorEvt.error || 'Error');
|
|
102
150
|
messages.push({ role: 'error', text: errorEvt.error || 'Error' });
|
|
103
151
|
saveState();
|
|
152
|
+
turnOutcome = 'error';
|
|
104
153
|
return;
|
|
105
154
|
}
|
|
106
155
|
|
|
@@ -130,6 +179,7 @@
|
|
|
130
179
|
} catch (e) {
|
|
131
180
|
if (e && (e.name === 'AbortError' || /aborted/i.test(String(e.message || '')))) {
|
|
132
181
|
userAborted = true;
|
|
182
|
+
turnOutcome = 'aborted';
|
|
133
183
|
if (streamedText) {
|
|
134
184
|
stream.finalize(streamedText);
|
|
135
185
|
messages.push({ role: 'assistant', text: streamedText, toolCalls: stream.getToolList() });
|
|
@@ -139,6 +189,7 @@
|
|
|
139
189
|
}
|
|
140
190
|
saveState();
|
|
141
191
|
} else {
|
|
192
|
+
turnOutcome = 'error';
|
|
142
193
|
var msg = 'Send failed: ' + (e && e.message ? e.message : e);
|
|
143
194
|
stream.replaceWithError(msg);
|
|
144
195
|
messages.push({ role: 'error', text: msg });
|
|
@@ -147,7 +198,8 @@
|
|
|
147
198
|
} finally {
|
|
148
199
|
setSending(false);
|
|
149
200
|
abortController = null;
|
|
150
|
-
if (!userAborted) inputEl.focus();
|
|
201
|
+
if (turnTabId === tabId && !userAborted) inputEl.focus();
|
|
202
|
+
_afterTurn(turnTabId, turnOutcome);
|
|
151
203
|
}
|
|
152
204
|
}
|
|
153
205
|
|
|
@@ -165,6 +217,10 @@
|
|
|
165
217
|
|
|
166
218
|
async function slimChatNew() {
|
|
167
219
|
if (sending) abortInFlight();
|
|
220
|
+
// New chat = fresh session: drop the leaving tab's pending queue (#7). The
|
|
221
|
+
// aborted in-flight turn's _afterTurn then finds an empty queue and no-ops.
|
|
222
|
+
delete queues[tabId];
|
|
223
|
+
queueSuspended[tabId] = false;
|
|
168
224
|
// Register a fresh tab in the shared cc-tabs store and switch slim onto
|
|
169
225
|
// it — classic's other tabs are left intact, and the previous session
|
|
170
226
|
// keeps its own server-side cc-sessions.json entry (no DELETE).
|
|
@@ -360,6 +360,59 @@
|
|
|
360
360
|
border: 1px solid var(--red);
|
|
361
361
|
font-size: var(--text-lg);
|
|
362
362
|
}
|
|
363
|
+
/* Queued messages (W-mqayw3x6): follow-ups submitted while a turn is still
|
|
364
|
+
streaming. The container is pinned to the bottom of the transcript; each
|
|
365
|
+
item is a greyed-out user bubble that fires FIFO on turn completion. */
|
|
366
|
+
.chat-queue {
|
|
367
|
+
display: flex;
|
|
368
|
+
flex-direction: column;
|
|
369
|
+
gap: 8px;
|
|
370
|
+
align-self: stretch;
|
|
371
|
+
}
|
|
372
|
+
.chat-msg.user.queued {
|
|
373
|
+
align-self: flex-end;
|
|
374
|
+
background: var(--surface2);
|
|
375
|
+
color: var(--muted);
|
|
376
|
+
border: 1px dashed var(--border);
|
|
377
|
+
opacity: 0.8;
|
|
378
|
+
display: flex;
|
|
379
|
+
align-items: center;
|
|
380
|
+
gap: 8px;
|
|
381
|
+
}
|
|
382
|
+
.chat-queue-text { white-space: pre-wrap; word-wrap: break-word; }
|
|
383
|
+
.chat-queue-badge {
|
|
384
|
+
font-size: var(--text-base);
|
|
385
|
+
text-transform: uppercase;
|
|
386
|
+
letter-spacing: 0.5px;
|
|
387
|
+
color: var(--muted);
|
|
388
|
+
border: 1px solid var(--border);
|
|
389
|
+
border-radius: var(--radius);
|
|
390
|
+
padding: 1px 6px;
|
|
391
|
+
flex-shrink: 0;
|
|
392
|
+
}
|
|
393
|
+
.chat-queue-dismiss {
|
|
394
|
+
background: none;
|
|
395
|
+
border: none;
|
|
396
|
+
color: var(--muted);
|
|
397
|
+
cursor: pointer;
|
|
398
|
+
font-size: var(--text-lg);
|
|
399
|
+
line-height: 1;
|
|
400
|
+
padding: 0 2px;
|
|
401
|
+
flex-shrink: 0;
|
|
402
|
+
}
|
|
403
|
+
.chat-queue-dismiss:hover { color: var(--red); }
|
|
404
|
+
.chat-queue-resume {
|
|
405
|
+
align-self: center;
|
|
406
|
+
background: var(--blue);
|
|
407
|
+
color: #fff;
|
|
408
|
+
border: none;
|
|
409
|
+
border-radius: var(--radius);
|
|
410
|
+
padding: 6px 14px;
|
|
411
|
+
font-size: var(--text-md);
|
|
412
|
+
font-weight: 600;
|
|
413
|
+
cursor: pointer;
|
|
414
|
+
}
|
|
415
|
+
.chat-queue-resume:hover { filter: brightness(1.1); }
|
|
363
416
|
.chat-input-wrap {
|
|
364
417
|
display: flex;
|
|
365
418
|
gap: 8px;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2176",
|
|
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"
|