@yemi33/minions 0.1.748 → 0.1.749
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 +2 -1
- package/dashboard/js/command-center.js +34 -31
- package/dashboard.js +16 -22
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,9 +8,9 @@ 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
|
-
|
|
12
|
-
|
|
13
|
-
var
|
|
11
|
+
// Per-tab sending state stored on tab objects: tab._sending, tab._queue, tab._abortController
|
|
12
|
+
// Legacy globals for backward compat (badge, drawer close check)
|
|
13
|
+
var _ccSending = false; // true if active tab is sending (UI indicator only)
|
|
14
14
|
// Clear stale sending state on page load — SSE streams don't survive refresh
|
|
15
15
|
try { localStorage.removeItem('cc-sending'); } catch {}
|
|
16
16
|
|
|
@@ -72,9 +72,10 @@ function _ccFindPinTarget(query) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
function ccAbort() {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
var tab = _ccActiveTab();
|
|
76
|
+
if (tab && tab._abortController) {
|
|
77
|
+
tab._abortController.abort();
|
|
78
|
+
tab._abortController = null;
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
|
|
@@ -114,11 +115,6 @@ function ccNewTab(skipServerReset) {
|
|
|
114
115
|
_ccTabs.push(tab);
|
|
115
116
|
_ccActiveTabId = tabId;
|
|
116
117
|
// New tab starts with null sessionId — server creates fresh session on first message
|
|
117
|
-
// Abort any in-flight request
|
|
118
|
-
ccAbort();
|
|
119
|
-
_ccSending = false;
|
|
120
|
-
_ccQueue = [];
|
|
121
|
-
try { localStorage.removeItem('cc-sending'); } catch {}
|
|
122
118
|
document.getElementById('cc-messages').innerHTML = '';
|
|
123
119
|
ccRenderTabBar();
|
|
124
120
|
ccUpdateSessionIndicator();
|
|
@@ -149,13 +145,13 @@ function ccSwitchTab(id) {
|
|
|
149
145
|
function ccCloseTab(id) {
|
|
150
146
|
var idx = _ccTabs.findIndex(function(t) { return t.id === id; });
|
|
151
147
|
if (idx === -1) return;
|
|
152
|
-
|
|
153
|
-
if (
|
|
148
|
+
var closingTab = _ccTabs.find(function(t) { return t.id === id; });
|
|
149
|
+
if (closingTab && closingTab._sending) {
|
|
154
150
|
if (!confirm('This tab has an active request. Close anyway?')) return;
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
151
|
+
if (closingTab._abortController) { closingTab._abortController.abort(); closingTab._abortController = null; }
|
|
152
|
+
closingTab._sending = false;
|
|
153
|
+
closingTab._queue = [];
|
|
154
|
+
_ccSending = (_ccTabs.some(function(t) { return t._sending; }));
|
|
159
155
|
}
|
|
160
156
|
_ccTabs.splice(idx, 1);
|
|
161
157
|
if (_ccActiveTabId === id) {
|
|
@@ -328,23 +324,26 @@ async function ccSend() {
|
|
|
328
324
|
if (!message) return;
|
|
329
325
|
input.value = '';
|
|
330
326
|
|
|
331
|
-
|
|
332
|
-
if (
|
|
333
|
-
|
|
327
|
+
var tab = _ccActiveTab();
|
|
328
|
+
if (!tab) return;
|
|
329
|
+
if (!tab._queue) tab._queue = [];
|
|
330
|
+
|
|
331
|
+
// If this tab is already processing, queue the message
|
|
332
|
+
if (tab._sending) {
|
|
333
|
+
tab._queue.push(message);
|
|
334
334
|
_renderQueueIndicator();
|
|
335
335
|
return;
|
|
336
336
|
}
|
|
337
337
|
var wasAborted = await _ccDoSend(message);
|
|
338
338
|
|
|
339
|
-
// Drain queue — send each queued message in order
|
|
340
|
-
while (
|
|
341
|
-
// After abort, pause to let server release ccInFlight guard before next send
|
|
339
|
+
// Drain this tab's queue — send each queued message in order
|
|
340
|
+
while (tab._queue && tab._queue.length > 0) {
|
|
342
341
|
if (wasAborted) {
|
|
343
|
-
|
|
342
|
+
tab._sending = true;
|
|
344
343
|
await new Promise(function(r) { setTimeout(r, 500); });
|
|
345
|
-
|
|
344
|
+
tab._sending = false;
|
|
346
345
|
}
|
|
347
|
-
var next =
|
|
346
|
+
var next = tab._queue.shift();
|
|
348
347
|
_renderQueueIndicator();
|
|
349
348
|
wasAborted = await _ccDoSend(next);
|
|
350
349
|
}
|
|
@@ -387,8 +386,12 @@ async function _ccDoSend(message, skipUserMsg) {
|
|
|
387
386
|
return;
|
|
388
387
|
}
|
|
389
388
|
|
|
390
|
-
|
|
391
|
-
|
|
389
|
+
var activeTab = _ccActiveTab();
|
|
390
|
+
var activeTabId = _ccActiveTabId;
|
|
391
|
+
if (!activeTab) return;
|
|
392
|
+
activeTab._sending = true;
|
|
393
|
+
activeTab._abortController = new AbortController();
|
|
394
|
+
_ccSending = true; // UI indicator
|
|
392
395
|
var _wasAborted = false;
|
|
393
396
|
try { localStorage.setItem('cc-sending', JSON.stringify({ sending: true, startedAt: Date.now() })); } catch {}
|
|
394
397
|
|
|
@@ -461,7 +464,7 @@ async function _ccDoSend(message, skipUserMsg) {
|
|
|
461
464
|
var res = await fetch('/api/command-center/stream', {
|
|
462
465
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
463
466
|
body: JSON.stringify({ message: message, tabId: activeTabId, sessionId: activeTab.sessionId || null }),
|
|
464
|
-
signal:
|
|
467
|
+
signal: activeTab._abortController ? activeTab._abortController.signal : AbortSignal.timeout(960000)
|
|
465
468
|
});
|
|
466
469
|
|
|
467
470
|
if (!res.ok) {
|
|
@@ -574,8 +577,8 @@ async function _ccDoSend(message, skipUserMsg) {
|
|
|
574
577
|
' <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>');
|
|
575
578
|
}
|
|
576
579
|
} finally {
|
|
577
|
-
|
|
578
|
-
|
|
580
|
+
if (activeTab) { activeTab._sending = false; activeTab._abortController = null; }
|
|
581
|
+
_ccSending = (_ccTabs.some(function(t) { return t._sending; }));
|
|
579
582
|
try { clearInterval(phaseTimer); } catch { /* may not be defined if error before reader */ }
|
|
580
583
|
try { localStorage.removeItem('cc-sending'); } catch {}
|
|
581
584
|
// Show notification badge on CC button if drawer is closed
|
package/dashboard.js
CHANGED
|
@@ -464,8 +464,7 @@ setInterval(() => {
|
|
|
464
464
|
const CC_SESSION_EXPIRY_MS = 2 * 60 * 60 * 1000; // 2 hours
|
|
465
465
|
const CC_SESSION_MAX_TURNS = Infinity;
|
|
466
466
|
let ccSession = { sessionId: null, createdAt: null, lastActiveAt: null, turnCount: 0 };
|
|
467
|
-
|
|
468
|
-
let ccInFlightSince = 0; // timestamp — auto-release stuck guard
|
|
467
|
+
const ccInFlightTabs = new Set(); // per-tab in-flight tracking for parallel CC requests
|
|
469
468
|
const CC_INFLIGHT_TIMEOUT_MS = 2 * 60 * 1000; // 2 minutes — auto-release if request hangs
|
|
470
469
|
|
|
471
470
|
// _ccPromptHash computed after CC_STATIC_SYSTEM_PROMPT is defined (see below)
|
|
@@ -3250,7 +3249,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3250
3249
|
|
|
3251
3250
|
async function handleCommandCenterNewSession(req, res) {
|
|
3252
3251
|
ccSession = { sessionId: null, createdAt: null, lastActiveAt: null, turnCount: 0 };
|
|
3253
|
-
|
|
3252
|
+
ccInFlightTabs.clear(); // Reset all in-flight guards
|
|
3254
3253
|
safeWrite(path.join(ENGINE_DIR, 'cc-session.json'), ccSession);
|
|
3255
3254
|
return jsonReply(res, 200, { ok: true });
|
|
3256
3255
|
}
|
|
@@ -3275,13 +3274,12 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3275
3274
|
const body = await readBody(req);
|
|
3276
3275
|
if (!body.message) return jsonReply(res, 400, { error: 'message required' });
|
|
3277
3276
|
|
|
3278
|
-
//
|
|
3279
|
-
|
|
3280
|
-
|
|
3277
|
+
// Per-tab concurrency guard
|
|
3278
|
+
const tabId = body.tabId || 'default';
|
|
3279
|
+
if (ccInFlightTabs.has(tabId)) {
|
|
3280
|
+
return jsonReply(res, 429, { error: 'This tab is already processing — wait or open a new tab.' });
|
|
3281
3281
|
}
|
|
3282
|
-
|
|
3283
|
-
ccInFlight = true;
|
|
3284
|
-
ccInFlightSince = Date.now();
|
|
3282
|
+
ccInFlightTabs.add(tabId);
|
|
3285
3283
|
|
|
3286
3284
|
try {
|
|
3287
3285
|
if (body.sessionId && body.sessionId !== ccSession.sessionId) {
|
|
@@ -3309,10 +3307,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3309
3307
|
|
|
3310
3308
|
return jsonReply(res, 200, { ...parseCCActions(result.text), sessionId: ccSession.sessionId, newSession: !wasResume });
|
|
3311
3309
|
} finally {
|
|
3312
|
-
|
|
3313
|
-
ccInFlightSince = 0;
|
|
3310
|
+
ccInFlightTabs.delete(tabId);
|
|
3314
3311
|
}
|
|
3315
|
-
} catch (e) {
|
|
3312
|
+
} catch (e) { ccInFlightTabs.delete(tabId); return jsonReply(res, 500, { error: e.message }); }
|
|
3316
3313
|
}
|
|
3317
3314
|
|
|
3318
3315
|
async function handleCommandCenterStream(req, res) {
|
|
@@ -3320,17 +3317,16 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3320
3317
|
try {
|
|
3321
3318
|
const body = await readBody(req);
|
|
3322
3319
|
if (!body.message) { res.statusCode = 400; res.end('message required'); return; }
|
|
3323
|
-
|
|
3324
|
-
|
|
3320
|
+
const tabId = body.tabId || 'default';
|
|
3321
|
+
if (ccInFlightTabs.has(tabId)) {
|
|
3322
|
+
res.statusCode = 429; res.end('This tab is already processing'); return;
|
|
3325
3323
|
}
|
|
3326
|
-
|
|
3327
|
-
ccInFlight = true;
|
|
3328
|
-
ccInFlightSince = Date.now();
|
|
3324
|
+
ccInFlightTabs.add(tabId);
|
|
3329
3325
|
|
|
3330
3326
|
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' });
|
|
3331
3327
|
let _ccStreamAbort = null;
|
|
3332
3328
|
// Kill LLM process immediately if client disconnects mid-stream
|
|
3333
|
-
req.on('close', () => {
|
|
3329
|
+
req.on('close', () => { ccInFlightTabs.delete(tabId); if (_ccStreamAbort) _ccStreamAbort(); });
|
|
3334
3330
|
|
|
3335
3331
|
try {
|
|
3336
3332
|
// Session management — per-tab: use sessionId from request if provided
|
|
@@ -3417,12 +3413,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3417
3413
|
res.write('data: ' + JSON.stringify({ type: 'done', text: displayText, actions, sessionId: ccSession.sessionId, newSession: !wasResume }) + '\n\n');
|
|
3418
3414
|
res.end();
|
|
3419
3415
|
} finally {
|
|
3420
|
-
|
|
3421
|
-
ccInFlightSince = 0;
|
|
3416
|
+
ccInFlightTabs.delete(tabId);
|
|
3422
3417
|
}
|
|
3423
3418
|
} catch (e) {
|
|
3424
|
-
|
|
3425
|
-
ccInFlightSince = 0;
|
|
3419
|
+
ccInFlightTabs.delete(tabId);
|
|
3426
3420
|
try { res.write('data: ' + JSON.stringify({ type: 'error', error: e.message }) + '\n\n'); } catch {}
|
|
3427
3421
|
try { res.end(); } catch {}
|
|
3428
3422
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.749",
|
|
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"
|