@yemi33/minions 0.1.1694 → 0.1.1695
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 +6 -2
- package/dashboard/js/command-center.js +122 -29
- package/engine/copilot-models.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.1695 (2026-05-04)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
- harden command center stream retry (#2016)
|
|
7
|
+
|
|
8
|
+
## 0.1.1693 (2026-05-04)
|
|
4
9
|
|
|
5
10
|
### Features
|
|
6
|
-
- remove stale permission mode settings (#2009)
|
|
7
11
|
- sync review verdict PR status (#2008)
|
|
8
12
|
|
|
9
13
|
## 0.1.1691 (2026-05-04)
|
|
@@ -8,6 +8,7 @@ var CC_TITLE_MAX_LENGTH = 40;
|
|
|
8
8
|
var _ccTabs = []; // [{id, title, sessionId, messages: [{role, html}]}]
|
|
9
9
|
var _ccActiveTabId = null;
|
|
10
10
|
var _ccOpen = false;
|
|
11
|
+
var _ccRetrySeq = 0;
|
|
11
12
|
// Per-tab sending state stored on tab objects: tab._sending, tab._queue, tab._abortController
|
|
12
13
|
// Legacy globals for backward compat (badge, drawer close check)
|
|
13
14
|
var _ccSending = false; // true if active tab is sending (UI indicator only)
|
|
@@ -139,6 +140,79 @@ async function _ccDashboardHealth() {
|
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
|
|
143
|
+
function _ccIsReconnectableStreamError(err) {
|
|
144
|
+
if (!err) return false;
|
|
145
|
+
var name = String(err.name || '').toLowerCase();
|
|
146
|
+
var message = String(err.message || err || '').toLowerCase();
|
|
147
|
+
if (name === 'aborterror') return true;
|
|
148
|
+
return message === 'failed to fetch'
|
|
149
|
+
|| message === 'load failed'
|
|
150
|
+
|| message.includes('networkerror')
|
|
151
|
+
|| message.includes('network request failed')
|
|
152
|
+
|| message.includes('the internet connection appears to be offline')
|
|
153
|
+
|| message.includes('network connection was lost')
|
|
154
|
+
|| message.includes('cancelled')
|
|
155
|
+
|| message.includes('canceled')
|
|
156
|
+
|| message.includes('aborted');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function _ccJsArg(value) {
|
|
160
|
+
return escHtml(JSON.stringify(value == null ? '' : String(value)));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function _ccStoreRetryRequest(tab, tabId, message) {
|
|
164
|
+
if (!tab) return { id: '', tabId: tabId || '', message: String(message || '') };
|
|
165
|
+
var id = 'cc-retry-' + Date.now().toString(36) + '-' + (++_ccRetrySeq);
|
|
166
|
+
var request = { id: id, tabId: tabId || tab.id, message: String(message || ''), createdAt: Date.now() };
|
|
167
|
+
if (!tab._retryRequests) tab._retryRequests = {};
|
|
168
|
+
tab._retryRequests[id] = request;
|
|
169
|
+
tab._lastRetryRequestId = id;
|
|
170
|
+
tab._retryRequest = request;
|
|
171
|
+
return request;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function _ccFindRetryRequest(tab, retryId) {
|
|
175
|
+
if (!tab) return null;
|
|
176
|
+
if (retryId && tab._retryRequests && tab._retryRequests[retryId]) return tab._retryRequests[retryId];
|
|
177
|
+
if (tab._retryRequest) return tab._retryRequest;
|
|
178
|
+
if (tab._lastRetryRequestId && tab._retryRequests) return tab._retryRequests[tab._lastRetryRequestId] || null;
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function _ccForgetRetryRequest(tab, retryId) {
|
|
183
|
+
if (!tab || !retryId) return;
|
|
184
|
+
if (tab._retryRequests) delete tab._retryRequests[retryId];
|
|
185
|
+
if (tab._lastRetryRequestId === retryId) delete tab._lastRetryRequestId;
|
|
186
|
+
if (tab._retryRequest && tab._retryRequest.id === retryId) delete tab._retryRequest;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function _ccRemoveRetryMessage(tab, retryId) {
|
|
190
|
+
var removed = false;
|
|
191
|
+
if (tab && retryId) {
|
|
192
|
+
for (var i = tab.messages.length - 1; i >= 0; i--) {
|
|
193
|
+
if (tab.messages[i] && tab.messages[i]._retryId === retryId) {
|
|
194
|
+
tab.messages.splice(i, 1);
|
|
195
|
+
removed = true;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (retryId && tab && tab.id === _ccActiveTabId) {
|
|
201
|
+
var msgs = document.getElementById('cc-messages');
|
|
202
|
+
if (msgs) {
|
|
203
|
+
Array.prototype.slice.call(msgs.children).some(function(child) {
|
|
204
|
+
if (child.getAttribute && child.getAttribute('data-cc-retry-id') === retryId) {
|
|
205
|
+
child.remove();
|
|
206
|
+
removed = true;
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
return false;
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return removed;
|
|
214
|
+
}
|
|
215
|
+
|
|
142
216
|
function _ccFindPinTarget(query) {
|
|
143
217
|
for (var i = 0; i < (inboxData || []).length; i++) {
|
|
144
218
|
if (inboxData[i].name.toLowerCase().includes(query)) {
|
|
@@ -159,6 +233,7 @@ function _ccFindPinTarget(query) {
|
|
|
159
233
|
function ccAbort() {
|
|
160
234
|
var tab = _ccActiveTab();
|
|
161
235
|
if (tab && tab._abortController) {
|
|
236
|
+
tab._userAborted = true;
|
|
162
237
|
try {
|
|
163
238
|
fetch('/api/command-center/abort', {
|
|
164
239
|
method: 'POST',
|
|
@@ -290,6 +365,7 @@ function ccCloseTab(id) {
|
|
|
290
365
|
body: JSON.stringify({ tabId: id })
|
|
291
366
|
}).catch(function() {});
|
|
292
367
|
} catch {}
|
|
368
|
+
closingTab._userAborted = true;
|
|
293
369
|
if (closingTab._abortController) { closingTab._abortController.abort(); closingTab._abortController = null; }
|
|
294
370
|
closingTab._sending = false;
|
|
295
371
|
closingTab._queue = [];
|
|
@@ -406,7 +482,7 @@ function ccUpdateSessionIndicator() {
|
|
|
406
482
|
}
|
|
407
483
|
}
|
|
408
484
|
|
|
409
|
-
function ccAddMessage(role, html, skipSave, targetTabId) {
|
|
485
|
+
function ccAddMessage(role, html, skipSave, targetTabId, meta) {
|
|
410
486
|
var isUser = role === 'user';
|
|
411
487
|
var isSystem = role === 'system';
|
|
412
488
|
var isAction = role === 'action';
|
|
@@ -418,6 +494,7 @@ function ccAddMessage(role, html, skipSave, targetTabId) {
|
|
|
418
494
|
var el = document.getElementById('cc-messages');
|
|
419
495
|
var div = document.createElement('div');
|
|
420
496
|
div.className = isAssistant ? 'cc-msg-assistant' : '';
|
|
497
|
+
if (meta && meta.retryId) div.setAttribute('data-cc-retry-id', meta.retryId);
|
|
421
498
|
div.style.cssText = 'padding:8px 12px;border-radius:8px;font-size:12px;line-height:1.6;max-width:95%;' +
|
|
422
499
|
(isUser ? 'background:var(--blue);color:#fff;align-self:flex-end' : isSystem ? 'align-self:center;max-width:100%' : isAction ? 'align-self:flex-start;padding:2px 0' : 'background:var(--surface2);color:var(--text);align-self:flex-start;border:1px solid var(--border);position:relative');
|
|
423
500
|
div.innerHTML = (isAssistant && !html.includes('color:var(--red)') && !html.includes('cc-queued-pill') ? llmCopyBtn() : '') + html;
|
|
@@ -428,7 +505,9 @@ function ccAddMessage(role, html, skipSave, targetTabId) {
|
|
|
428
505
|
if (!skipSave) {
|
|
429
506
|
var tab = targetTab;
|
|
430
507
|
if (tab) {
|
|
431
|
-
|
|
508
|
+
var msg = { role: role, html: html };
|
|
509
|
+
if (meta && meta.retryId) msg._retryId = meta.retryId;
|
|
510
|
+
tab.messages.push(msg);
|
|
432
511
|
// Auto-title from first user message
|
|
433
512
|
if (role === 'user' && tab.title === 'New chat') {
|
|
434
513
|
var tmp = document.createElement('div');
|
|
@@ -519,13 +598,14 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
|
|
|
519
598
|
activeTab._sending = true;
|
|
520
599
|
activeTab._sendStartedAt = Date.now();
|
|
521
600
|
activeTab._abortController = new AbortController();
|
|
601
|
+
activeTab._userAborted = false;
|
|
522
602
|
_ccSending = true;
|
|
523
603
|
ccRenderTabBar();
|
|
524
604
|
var _wasAborted = false;
|
|
525
605
|
try { localStorage.setItem('cc-sending', JSON.stringify({ sending: true, startedAt: Date.now() })); } catch {}
|
|
526
606
|
|
|
527
607
|
// Scoped helper — always targets the originating tab, even if user switches tabs
|
|
528
|
-
function addMsg(role, html, skipSave) { ccAddMessage(role, html, skipSave, activeTabId); }
|
|
608
|
+
function addMsg(role, html, skipSave, meta) { ccAddMessage(role, html, skipSave, activeTabId, meta); }
|
|
529
609
|
|
|
530
610
|
if (!skipUserMsg) addMsg('user', escHtml(message));
|
|
531
611
|
|
|
@@ -615,9 +695,11 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
|
|
|
615
695
|
var seconds = Math.round((Date.now() - ccStartTime) / 1000);
|
|
616
696
|
return '<div style="font-size:9px;color:var(--muted);margin-top:6px;display:flex;justify-content:flex-end;padding-right:30px">' + label.replace('{seconds}', seconds) + '</div>';
|
|
617
697
|
}
|
|
618
|
-
function _ccRetryControls(extraHtml, showReload) {
|
|
698
|
+
function _ccRetryControls(retryRequest, extraHtml, showReload) {
|
|
699
|
+
var retryTabId = retryRequest && retryRequest.tabId ? retryRequest.tabId : activeTabId;
|
|
700
|
+
var retryId = retryRequest && retryRequest.id ? retryRequest.id : '';
|
|
619
701
|
return (extraHtml || '') +
|
|
620
|
-
'<button onclick="ccRetryLast()" style="margin-top:6px;padding:4px 12px;background:var(--surface2);border:1px solid var(--border);border-radius:4px;color:var(--blue);cursor:pointer;font-size:11px">Retry</button>' +
|
|
702
|
+
'<button onclick="ccRetryLast(' + _ccJsArg(retryTabId) + ',' + _ccJsArg(retryId) + ')" style="margin-top:6px;padding:4px 12px;background:var(--surface2);border:1px solid var(--border);border-radius:4px;color:var(--blue);cursor:pointer;font-size:11px">Retry</button>' +
|
|
621
703
|
(showReload ? ' <button onclick="location.reload()" style="margin-top:6px;padding:4px 12px;background:var(--orange);color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px">Reload Page</button>' : '') +
|
|
622
704
|
' <button onclick="ccNewTab()" style="margin-top:6px;padding:4px 12px;background:var(--surface2);border:1px solid var(--border);border-radius:4px;color:var(--muted);cursor:pointer;font-size:11px">New Session</button>';
|
|
623
705
|
}
|
|
@@ -748,11 +830,12 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
|
|
|
748
830
|
if (!consume.interrupted) break;
|
|
749
831
|
if (!consume.reconnectable || reconnectAttempts >= 2) {
|
|
750
832
|
_cleanupStreamDiv();
|
|
751
|
-
var streamEndedHint = '<div style="font-size:10px;color:var(--muted);margin-top:4px">The response stream ended before completion. Retry to
|
|
833
|
+
var streamEndedHint = '<div style="font-size:10px;color:var(--muted);margin-top:4px">The response stream ended before completion. Retry to resend the interrupted message.</div>';
|
|
834
|
+
var streamEndedRetry = _ccStoreRetryRequest(activeTab, activeTabId, message);
|
|
752
835
|
if (streamedText) {
|
|
753
|
-
addMsg('assistant', renderMd(streamedText) + _ccElapsedFooter('Stream interrupted after {seconds}s') + _ccRetryControls(streamEndedHint, false));
|
|
836
|
+
addMsg('assistant', renderMd(streamedText) + _ccElapsedFooter('Stream interrupted after {seconds}s') + _ccRetryControls(streamEndedRetry, streamEndedHint, false), false, { retryId: streamEndedRetry.id });
|
|
754
837
|
} else {
|
|
755
|
-
addMsg('assistant', '<span style="color:var(--red)">The response stream ended before completion.</span>' + _ccRetryControls(streamEndedHint, false));
|
|
838
|
+
addMsg('assistant', '<span style="color:var(--red)">The response stream ended before completion.</span>' + _ccRetryControls(streamEndedRetry, streamEndedHint, false), false, { retryId: streamEndedRetry.id });
|
|
756
839
|
}
|
|
757
840
|
break;
|
|
758
841
|
}
|
|
@@ -762,8 +845,9 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
|
|
|
762
845
|
var reconnectHint = reconnectHealth.restarted
|
|
763
846
|
? '<div style="font-size:10px;color:var(--muted);margin-top:4px">Dashboard restarted while this response was streaming. Reload the page to reconnect to the new instance.</div>'
|
|
764
847
|
: '<div style="font-size:10px;color:var(--muted);margin-top:4px">The request stream was interrupted, but the dashboard is still reachable. Retry or start a new session.</div>';
|
|
848
|
+
var reconnectRetry = _ccStoreRetryRequest(activeTab, activeTabId, message);
|
|
765
849
|
addMsg('assistant', (streamedText ? renderMd(streamedText) + _ccElapsedFooter('Stream interrupted after {seconds}s') : '') +
|
|
766
|
-
_ccRetryControls(reconnectHint, reconnectHealth.restarted));
|
|
850
|
+
_ccRetryControls(reconnectRetry, reconnectHint, reconnectHealth.restarted), false, { retryId: reconnectRetry.id });
|
|
767
851
|
break;
|
|
768
852
|
}
|
|
769
853
|
reconnectAttempts++;
|
|
@@ -776,7 +860,7 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
|
|
|
776
860
|
}
|
|
777
861
|
} catch (e) {
|
|
778
862
|
_cleanupStreamDiv();
|
|
779
|
-
if (
|
|
863
|
+
if (activeTab && activeTab._userAborted) {
|
|
780
864
|
_wasAborted = true;
|
|
781
865
|
if (streamedText) {
|
|
782
866
|
addMsg('assistant', renderMd(streamedText) + _ccElapsedFooter('Stopped after {seconds}s'));
|
|
@@ -784,7 +868,7 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
|
|
|
784
868
|
addMsg('assistant', '<span style="color:var(--red);font-size:11px">Stopped</span>');
|
|
785
869
|
}
|
|
786
870
|
} else {
|
|
787
|
-
var isNetworkError = e
|
|
871
|
+
var isNetworkError = _ccIsReconnectableStreamError(e);
|
|
788
872
|
var dashboardHealth = isNetworkError ? await _ccDashboardHealth() : { reachable: false, restarted: false };
|
|
789
873
|
var connectionHint = '';
|
|
790
874
|
if (isNetworkError) {
|
|
@@ -794,12 +878,13 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
|
|
|
794
878
|
? '<div style="font-size:10px;color:var(--muted);margin-top:4px">The request stream was interrupted, but the dashboard is still reachable. Retry or start a new session.</div>'
|
|
795
879
|
: '<div style="font-size:10px;color:var(--muted);margin-top:4px">Dashboard connection lost. Reload the page to reconnect.</div>';
|
|
796
880
|
}
|
|
881
|
+
var errorRetry = _ccStoreRetryRequest(activeTab, activeTabId, message);
|
|
797
882
|
addMsg('assistant', (streamedText ? renderMd(streamedText) + _ccElapsedFooter('Stream interrupted after {seconds}s') : '') +
|
|
798
883
|
'<span style="color:var(--red)">Error: ' + escHtml(e.message) + '</span>' +
|
|
799
|
-
_ccRetryControls(connectionHint, isNetworkError && (!dashboardHealth.reachable || dashboardHealth.restarted)));
|
|
884
|
+
_ccRetryControls(errorRetry, connectionHint, isNetworkError && (!dashboardHealth.reachable || dashboardHealth.restarted)), false, { retryId: errorRetry.id });
|
|
800
885
|
}
|
|
801
886
|
} finally {
|
|
802
|
-
if (activeTab) { activeTab._sending = false; activeTab._abortController = null; activeTab._429retries = 0; delete activeTab._streamedText; delete activeTab._toolsUsed; delete activeTab._sendStartedAt; }
|
|
887
|
+
if (activeTab) { activeTab._sending = false; activeTab._abortController = null; activeTab._429retries = 0; delete activeTab._streamedText; delete activeTab._toolsUsed; delete activeTab._sendStartedAt; delete activeTab._userAborted; }
|
|
803
888
|
_ccSending = (_ccTabs.some(function(t) { return t._sending; }));
|
|
804
889
|
// Mark tab unread if response completed on a background tab or while drawer is closed
|
|
805
890
|
if (activeTab && !_wasAborted && (activeTab.id !== _ccActiveTabId || !_ccOpen)) activeTab._unread = true;
|
|
@@ -813,28 +898,36 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
|
|
|
813
898
|
return _wasAborted;
|
|
814
899
|
}
|
|
815
900
|
|
|
816
|
-
function ccRetryLast() {
|
|
817
|
-
|
|
818
|
-
var tab = _ccActiveTab();
|
|
901
|
+
function ccRetryLast(tabId, retryId) {
|
|
902
|
+
var tab = tabId ? _ccTabs.find(function(t) { return t.id === tabId; }) : _ccActiveTab();
|
|
819
903
|
if (!tab) return;
|
|
820
|
-
var
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
904
|
+
var retryRequest = _ccFindRetryRequest(tab, retryId);
|
|
905
|
+
var text = retryRequest ? retryRequest.message : '';
|
|
906
|
+
if (!text) {
|
|
907
|
+
var last = tab.messages.filter(function(m) { return m.role === 'user'; }).pop();
|
|
908
|
+
if (!last) return;
|
|
909
|
+
// Backward-compatible fallback for retry buttons rendered before retry context existed.
|
|
910
|
+
var tmp = document.createElement('div');
|
|
911
|
+
tmp.innerHTML = last.html;
|
|
912
|
+
text = tmp.textContent || tmp.innerText || '';
|
|
913
|
+
}
|
|
826
914
|
if (!text.trim()) return;
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
915
|
+
var removed = _ccRemoveRetryMessage(tab, retryId);
|
|
916
|
+
if (!removed && tab.id === _ccActiveTabId) {
|
|
917
|
+
// Legacy fallback for old controls without retry ids: remove the visible error card.
|
|
918
|
+
var el = document.getElementById('cc-messages');
|
|
919
|
+
if (el?.lastElementChild) el.lastElementChild.remove();
|
|
920
|
+
tab.messages = tab.messages.slice(0, -1);
|
|
921
|
+
}
|
|
922
|
+
_ccForgetRetryRequest(tab, retryId);
|
|
923
|
+
ccSaveState();
|
|
831
924
|
// Resend, then drain queue
|
|
832
|
-
_ccDoSend(text.trim()).then(async function() {
|
|
833
|
-
var retryTab =
|
|
925
|
+
_ccDoSend(text.trim(), false, tab.id).then(async function() {
|
|
926
|
+
var retryTab = _ccTabs.find(function(t) { return t.id === tab.id; });
|
|
834
927
|
while (retryTab && retryTab._queue && retryTab._queue.length > 0) {
|
|
835
928
|
var next = retryTab._queue.shift();
|
|
836
929
|
_renderQueueIndicator();
|
|
837
|
-
await _ccDoSend(next);
|
|
930
|
+
await _ccDoSend(next, false, retryTab.id);
|
|
838
931
|
}
|
|
839
932
|
});
|
|
840
933
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1695",
|
|
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"
|