@yemi33/minions 0.1.1026 → 0.1.1027
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/modal-qa.js +326 -104
- package/dashboard/js/modal.js +4 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.1027 (2026-04-16)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
6
|
- route implement items to dedicated implement playbook (#1115)
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
- harden audited state transitions
|
|
26
26
|
|
|
27
27
|
### Other
|
|
28
|
+
- Fix doc chat session isolation
|
|
28
29
|
- test(pipeline): add unit tests for CRUD, stage execution, run lifecycle (#1162)
|
|
29
30
|
- chore: test publish after removing stale status check
|
|
30
31
|
- chore: trigger publish test
|
package/dashboard/js/modal-qa.js
CHANGED
|
@@ -40,6 +40,7 @@ function _renderQaUserMessage(thread, message, selection) {
|
|
|
40
40
|
_showThreadWrap();
|
|
41
41
|
}
|
|
42
42
|
const _qaSessions = new Map(); // persist conversations across modal open/close {key → {history, threadHtml}}
|
|
43
|
+
const _qaRuntime = new Map(); // key → {history, processing, abortController, queue}
|
|
43
44
|
// Restore from localStorage
|
|
44
45
|
try {
|
|
45
46
|
const saved = JSON.parse(localStorage.getItem('qa-sessions') || '{}');
|
|
@@ -55,6 +56,175 @@ function _saveQaSessions() {
|
|
|
55
56
|
} catch { /* localStorage might be full */ }
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
function _qaCloneQueue(queue) {
|
|
60
|
+
return Array.isArray(queue) ? queue.map(item => ({ ...item })) : [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function _qaGetRuntime(key) {
|
|
64
|
+
if (!key) return null;
|
|
65
|
+
let runtime = _qaRuntime.get(key);
|
|
66
|
+
if (!runtime) {
|
|
67
|
+
const prior = _qaSessions.get(key);
|
|
68
|
+
runtime = {
|
|
69
|
+
history: Array.isArray(prior?.history) ? prior.history.slice() : [],
|
|
70
|
+
processing: false,
|
|
71
|
+
abortController: null,
|
|
72
|
+
queue: _qaCloneQueue(prior?.queue),
|
|
73
|
+
};
|
|
74
|
+
_qaRuntime.set(key, runtime);
|
|
75
|
+
}
|
|
76
|
+
return runtime;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function _qaThreadEl() {
|
|
80
|
+
return document.getElementById('modal-qa-thread');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function _qaThreadHtml() {
|
|
84
|
+
return (_qaThreadEl() || {}).innerHTML || '';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function _qaIsActiveSession(key) {
|
|
88
|
+
return !!key && _qaSessionKey === key && document.getElementById('modal')?.classList?.contains('open');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function _qaSyncActiveRuntime() {
|
|
92
|
+
if (!_qaSessionKey) return;
|
|
93
|
+
const runtime = _qaGetRuntime(_qaSessionKey);
|
|
94
|
+
if (!runtime) return;
|
|
95
|
+
runtime.history = _qaHistory.slice();
|
|
96
|
+
runtime.processing = _qaProcessing;
|
|
97
|
+
runtime.abortController = _qaAbortController || null;
|
|
98
|
+
runtime.queue = _qaCloneQueue(_qaQueue);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function _qaPersistSession(key, { threadHtml, docContext, filePath, history, queue } = {}) {
|
|
102
|
+
if (!key) return;
|
|
103
|
+
const runtime = _qaGetRuntime(key);
|
|
104
|
+
const prior = _qaSessions.get(key) || {};
|
|
105
|
+
const persistedHistory = Array.isArray(history)
|
|
106
|
+
? history.slice()
|
|
107
|
+
: Array.isArray(runtime?.history)
|
|
108
|
+
? runtime.history.slice()
|
|
109
|
+
: Array.isArray(prior.history)
|
|
110
|
+
? prior.history.slice()
|
|
111
|
+
: [];
|
|
112
|
+
_qaSessions.set(key, {
|
|
113
|
+
history: persistedHistory,
|
|
114
|
+
threadHtml: threadHtml != null ? threadHtml : (prior.threadHtml || ''),
|
|
115
|
+
docContext: docContext ? { ...docContext } : (prior.docContext ? { ...prior.docContext } : { title: '', content: '', selection: '' }),
|
|
116
|
+
filePath: filePath !== undefined ? filePath : prior.filePath,
|
|
117
|
+
queue: Array.isArray(queue) ? _qaCloneQueue(queue) : _qaCloneQueue(runtime?.queue),
|
|
118
|
+
});
|
|
119
|
+
_saveQaSessions();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function _qaSaveActiveSessionState() {
|
|
123
|
+
if (!_qaSessionKey) return;
|
|
124
|
+
_qaSyncActiveRuntime();
|
|
125
|
+
_qaPersistSession(_qaSessionKey, {
|
|
126
|
+
threadHtml: _qaThreadHtml(),
|
|
127
|
+
docContext: { ..._modalDocContext },
|
|
128
|
+
filePath: _modalFilePath,
|
|
129
|
+
history: _qaHistory,
|
|
130
|
+
queue: _qaQueue,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function _qaLoadSessionState(key) {
|
|
135
|
+
const prior = _qaSessions.get(key);
|
|
136
|
+
const runtime = _qaGetRuntime(key);
|
|
137
|
+
_qaHistory = Array.isArray(runtime?.history) && runtime.history.length
|
|
138
|
+
? runtime.history.slice()
|
|
139
|
+
: Array.isArray(prior?.history)
|
|
140
|
+
? prior.history.slice()
|
|
141
|
+
: [];
|
|
142
|
+
_qaProcessing = !!runtime?.processing;
|
|
143
|
+
_qaAbortController = runtime?.abortController || null;
|
|
144
|
+
_qaQueue = _qaCloneQueue(runtime?.queue);
|
|
145
|
+
const thread = _qaThreadEl();
|
|
146
|
+
if (thread) {
|
|
147
|
+
const tmp = document.createElement('div');
|
|
148
|
+
tmp.innerHTML = prior?.threadHtml || '';
|
|
149
|
+
if (!_qaProcessing) tmp.querySelectorAll('.modal-qa-loading').forEach(el => el.remove());
|
|
150
|
+
thread.innerHTML = tmp.innerHTML;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function _qaResetActiveState() {
|
|
155
|
+
_qaHistory = [];
|
|
156
|
+
_qaProcessing = false;
|
|
157
|
+
_qaAbortController = null;
|
|
158
|
+
_qaQueue = [];
|
|
159
|
+
_qaSessionKey = '';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function _qaBuildUserMessageHtml(message, selection) {
|
|
163
|
+
let qHtml = '<div class="modal-qa-q">' + escHtml(message);
|
|
164
|
+
if (selection) {
|
|
165
|
+
qHtml += '<span class="selection-ref">Re: "' + escHtml(selection.slice(0, 100)) + ((selection.length > 100) ? '...' : '') + '"</span>';
|
|
166
|
+
}
|
|
167
|
+
qHtml += '</div>';
|
|
168
|
+
return qHtml;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function _qaBuildQueuedHtml(message) {
|
|
172
|
+
const preview = escHtml(message.length > 60 ? message.slice(0, 57) + '...' : message);
|
|
173
|
+
return '<div class="qa-queued-item" style="color:var(--muted);font-size:10px;padding:4px 8px">Queued: "' + preview + '"</div>';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function _qaBuildLoadingHtml(loadingId, queueCount) {
|
|
177
|
+
const qaQueueBadge = queueCount > 0 ? ' <span style="font-size:9px;color:var(--muted);background:var(--surface);padding:1px 5px;border-radius:8px;border:1px solid var(--border)">+' + queueCount + ' queued</span>' : '';
|
|
178
|
+
return '<div class="modal-qa-loading" id="' + loadingId + '">' +
|
|
179
|
+
'<div class="dot-pulse"><span></span><span></span><span></span></div> ' +
|
|
180
|
+
'<span id="' + loadingId + '-text">Thinking...</span> ' +
|
|
181
|
+
'<span id="' + loadingId + '-time" style="font-size:10px;color:var(--muted)"></span>' +
|
|
182
|
+
' <button onclick="qaAbort()" style="font-size:9px;padding:2px 8px;background:var(--surface2);border:1px solid var(--border);border-radius:4px;color:var(--red);cursor:pointer">Stop</button>' +
|
|
183
|
+
qaQueueBadge + '</div>';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function _qaBuildAssistantHtml(text, opts) {
|
|
187
|
+
const body = opts?.isError ? escHtml(text) : renderMd(text);
|
|
188
|
+
const style = opts?.isError
|
|
189
|
+
? 'color:' + (opts?.color || 'var(--red)')
|
|
190
|
+
: 'border-left-color:' + (opts?.borderColor || 'var(--blue)');
|
|
191
|
+
const pad = opts?.isError ? '' : 'padding-right:24px;';
|
|
192
|
+
return '<div class="modal-qa-a" style="' + style + '">' +
|
|
193
|
+
(opts?.isError ? '' : llmCopyBtn()) +
|
|
194
|
+
body +
|
|
195
|
+
'<div style="font-size:9px;color:var(--muted);margin-top:4px;text-align:right;' + pad + '">' + opts.elapsed + 's</div>' +
|
|
196
|
+
'</div>';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function _qaMutateThreadHtml(key, mutate) {
|
|
200
|
+
const tmp = document.createElement('div');
|
|
201
|
+
tmp.innerHTML = _qaIsActiveSession(key) ? _qaThreadHtml() : ((_qaSessions.get(key) || {}).threadHtml || '');
|
|
202
|
+
mutate(tmp);
|
|
203
|
+
const html = tmp.innerHTML;
|
|
204
|
+
if (_qaIsActiveSession(key)) {
|
|
205
|
+
const thread = _qaThreadEl();
|
|
206
|
+
if (thread) {
|
|
207
|
+
thread.innerHTML = html;
|
|
208
|
+
thread.scrollTop = thread.scrollHeight;
|
|
209
|
+
}
|
|
210
|
+
_showThreadWrap();
|
|
211
|
+
}
|
|
212
|
+
return html;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function _qaResumeQueuedMessages() {
|
|
216
|
+
if (!_qaSessionKey || _qaProcessing || _qaQueue.length === 0) return;
|
|
217
|
+
const next = _qaQueue.shift();
|
|
218
|
+
const thread = _qaThreadEl();
|
|
219
|
+
if (thread) {
|
|
220
|
+
const queuedEl = thread.querySelector('.qa-queued-item');
|
|
221
|
+
if (queuedEl) queuedEl.remove();
|
|
222
|
+
_renderQaUserMessage(thread, next.message, next.selection);
|
|
223
|
+
}
|
|
224
|
+
_qaSaveActiveSessionState();
|
|
225
|
+
_processQaMessage(next.message, next.selection);
|
|
226
|
+
}
|
|
227
|
+
|
|
58
228
|
function modalAskAboutSelection() {
|
|
59
229
|
document.getElementById('ask-selection-btn').style.display = 'none';
|
|
60
230
|
|
|
@@ -91,16 +261,14 @@ function clearQaSelection() {
|
|
|
91
261
|
function _initQaSession() {
|
|
92
262
|
var key = _modalFilePath || _modalDocContext.title || '';
|
|
93
263
|
if (!key || _qaSessionKey === key) return;
|
|
264
|
+
if (_qaSessionKey && _qaSessionKey !== key) _qaSaveActiveSessionState();
|
|
94
265
|
_qaSessionKey = key;
|
|
95
|
-
// Clear notification badge on the source card when reopening
|
|
96
266
|
const card = findCardForFile(_modalFilePath);
|
|
97
267
|
if (card) clearNotifBadge(card);
|
|
98
268
|
var prior = _qaSessions.get(key);
|
|
269
|
+
_qaLoadSessionState(key);
|
|
99
270
|
if (prior) {
|
|
100
|
-
_qaHistory = prior.history;
|
|
101
|
-
document.getElementById('modal-qa-thread').innerHTML = prior.threadHtml;
|
|
102
271
|
if (prior.docContext) {
|
|
103
|
-
// Preserve freshly-fetched content and title — prior session may have stale/empty content
|
|
104
272
|
const freshContent = _modalDocContext.content;
|
|
105
273
|
const freshTitle = _modalDocContext.title;
|
|
106
274
|
_modalDocContext = Object.assign({}, prior.docContext, {
|
|
@@ -111,11 +279,13 @@ function _initQaSession() {
|
|
|
111
279
|
}
|
|
112
280
|
if (prior.filePath) _modalFilePath = prior.filePath;
|
|
113
281
|
_showThreadWrap();
|
|
114
|
-
// Defer scroll — container just transitioned from display:none, layout not yet computed
|
|
115
282
|
requestAnimationFrame(function() {
|
|
116
283
|
var thread = document.getElementById('modal-qa-thread');
|
|
117
284
|
if (thread) thread.scrollTop = thread.scrollHeight;
|
|
118
285
|
});
|
|
286
|
+
if (_qaQueue.length > 0 && !_qaProcessing) {
|
|
287
|
+
setTimeout(_qaResumeQueuedMessages, 0);
|
|
288
|
+
}
|
|
119
289
|
} else {
|
|
120
290
|
_qaHistory = [];
|
|
121
291
|
document.getElementById('modal-qa-thread').innerHTML = '';
|
|
@@ -127,9 +297,13 @@ function _initQaSession() {
|
|
|
127
297
|
}
|
|
128
298
|
|
|
129
299
|
function clearQaConversation() {
|
|
300
|
+
const runtime = _qaGetRuntime(_qaSessionKey);
|
|
301
|
+
if (_qaAbortController) _qaAbortController.abort();
|
|
302
|
+
if (runtime?.abortController && runtime.abortController !== _qaAbortController) runtime.abortController.abort();
|
|
130
303
|
_qaHistory = [];
|
|
131
304
|
_qaQueue = [];
|
|
132
305
|
_qaProcessing = false;
|
|
306
|
+
_qaAbortController = null;
|
|
133
307
|
document.getElementById('modal-qa-thread').innerHTML = '';
|
|
134
308
|
var wrap = document.getElementById('modal-qa-thread-wrap');
|
|
135
309
|
var expandBar = document.getElementById('qa-expand-bar');
|
|
@@ -137,6 +311,7 @@ function clearQaConversation() {
|
|
|
137
311
|
if (expandBar) expandBar.style.display = 'none';
|
|
138
312
|
if (_qaSessionKey) {
|
|
139
313
|
_qaSessions.delete(_qaSessionKey);
|
|
314
|
+
_qaRuntime.set(_qaSessionKey, { history: [], processing: false, abortController: null, queue: [] });
|
|
140
315
|
_saveQaSessions();
|
|
141
316
|
}
|
|
142
317
|
}
|
|
@@ -164,7 +339,6 @@ function modalSend() {
|
|
|
164
339
|
var thread = document.getElementById('modal-qa-thread');
|
|
165
340
|
const selection = _modalDocContext.selection || '';
|
|
166
341
|
|
|
167
|
-
// Clear input immediately so user can type next message
|
|
168
342
|
input.value = '';
|
|
169
343
|
_modalDocContext.selection = '';
|
|
170
344
|
document.getElementById('modal-qa-pill').style.display = 'none';
|
|
@@ -174,63 +348,79 @@ function modalSend() {
|
|
|
174
348
|
showToast('cmd-toast', 'Queue full — wait for current response', false);
|
|
175
349
|
return;
|
|
176
350
|
}
|
|
177
|
-
// Queue the message — show only grey queued indicator (blue bubble shows when processing starts)
|
|
178
351
|
_qaQueue.push({ message, selection });
|
|
179
|
-
|
|
180
|
-
thread.insertAdjacentHTML('beforeend', '<div class="qa-queued-item" style="color:var(--muted);font-size:10px;padding:4px 8px">Queued: "' + preview + '"</div>');
|
|
352
|
+
thread.insertAdjacentHTML('beforeend', _qaBuildQueuedHtml(message));
|
|
181
353
|
thread.scrollTop = thread.scrollHeight;
|
|
182
354
|
_showThreadWrap();
|
|
355
|
+
_qaSaveActiveSessionState();
|
|
183
356
|
return;
|
|
184
357
|
}
|
|
185
358
|
|
|
186
|
-
// Show message in thread when processing starts (not when queued)
|
|
187
359
|
_renderQaUserMessage(thread, message, selection);
|
|
188
|
-
|
|
360
|
+
_qaSaveActiveSessionState();
|
|
189
361
|
_processQaMessage(message, selection);
|
|
190
362
|
}
|
|
191
363
|
|
|
192
|
-
async function _processQaMessage(message, selection) {
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
const
|
|
364
|
+
async function _processQaMessage(message, selection, opts) {
|
|
365
|
+
const sessionKey = opts?.sessionKey || _qaSessionKey;
|
|
366
|
+
if (!sessionKey) return;
|
|
367
|
+
const runtime = _qaGetRuntime(sessionKey);
|
|
368
|
+
if (!runtime) return;
|
|
369
|
+
|
|
370
|
+
const prior = _qaSessions.get(sessionKey);
|
|
371
|
+
const capturedFilePath = opts?.filePath !== undefined ? opts.filePath : (prior?.filePath || _modalFilePath);
|
|
372
|
+
const capturedDocContext = Object.assign({}, prior?.docContext || {}, opts?.docContext || (_qaIsActiveSession(sessionKey) ? _modalDocContext : {}));
|
|
373
|
+
const isActiveSession = _qaIsActiveSession(sessionKey);
|
|
374
|
+
|
|
375
|
+
runtime.processing = true;
|
|
376
|
+
const abortController = new AbortController();
|
|
377
|
+
runtime.abortController = abortController;
|
|
378
|
+
if (isActiveSession) {
|
|
379
|
+
_qaProcessing = true;
|
|
380
|
+
_qaAbortController = abortController;
|
|
381
|
+
}
|
|
200
382
|
|
|
201
|
-
// Show processing badge on the source card
|
|
202
383
|
const sourceCard = findCardForFile(capturedFilePath);
|
|
203
384
|
if (sourceCard) showNotifBadge(sourceCard, 'processing');
|
|
204
385
|
|
|
205
386
|
const loadingId = 'chat-loading-' + Date.now();
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
387
|
+
const loadingHtml = _qaBuildLoadingHtml(loadingId, runtime.queue.length);
|
|
388
|
+
const startThreadHtml = _qaMutateThreadHtml(sessionKey, tmp => {
|
|
389
|
+
tmp.insertAdjacentHTML('beforeend', loadingHtml);
|
|
390
|
+
});
|
|
391
|
+
_qaPersistSession(sessionKey, {
|
|
392
|
+
threadHtml: startThreadHtml,
|
|
393
|
+
docContext: capturedDocContext,
|
|
394
|
+
filePath: capturedFilePath,
|
|
395
|
+
history: runtime.history,
|
|
396
|
+
queue: runtime.queue,
|
|
397
|
+
});
|
|
215
398
|
|
|
216
|
-
const isPlanEdit =
|
|
399
|
+
const isPlanEdit = capturedFilePath && capturedFilePath.match(/^plans\/.*\.md$/);
|
|
217
400
|
const qaStartTime = Date.now();
|
|
218
401
|
const qaPhases = isPlanEdit
|
|
219
402
|
? [[0,'Reading plan...'],[3000,'Analyzing structure...'],[8000,'Researching context...'],[15000,'Drafting revisions...'],[30000,'Writing updated plan...'],[60000,'Still working (large document)...'],[120000,'Deep edit in progress...'],[300000,'Almost there...']]
|
|
220
403
|
: [[0,'Thinking...'],[3000,'Reading document...'],[8000,'Analyzing...'],[20000,'Still working...'],[60000,'Taking a while...']];
|
|
221
404
|
const qaTimer = setInterval(() => {
|
|
222
405
|
const elapsed = Date.now() - qaStartTime;
|
|
223
|
-
const timeEl = document.getElementById(loadingId + '-time');
|
|
224
|
-
const textEl = document.getElementById(loadingId + '-text');
|
|
406
|
+
const timeEl = _qaIsActiveSession(sessionKey) ? document.getElementById(loadingId + '-time') : null;
|
|
407
|
+
const textEl = _qaIsActiveSession(sessionKey) ? document.getElementById(loadingId + '-text') : null;
|
|
225
408
|
if (timeEl) timeEl.textContent = Math.floor(elapsed / 1000) + 's';
|
|
226
|
-
if (textEl) {
|
|
409
|
+
if (textEl) {
|
|
410
|
+
for (let i = qaPhases.length - 1; i >= 0; i--) {
|
|
411
|
+
if (elapsed >= qaPhases[i][0]) {
|
|
412
|
+
textEl.textContent = qaPhases[i][1];
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
227
417
|
}, 500);
|
|
228
418
|
|
|
229
419
|
try {
|
|
230
420
|
const res = await fetch('/api/doc-chat', {
|
|
231
421
|
method: 'POST',
|
|
232
422
|
headers: { 'Content-Type': 'application/json' },
|
|
233
|
-
signal:
|
|
423
|
+
signal: abortController.signal,
|
|
234
424
|
body: JSON.stringify({
|
|
235
425
|
message,
|
|
236
426
|
document: capturedDocContext.content,
|
|
@@ -242,109 +432,141 @@ async function _processQaMessage(message, selection) {
|
|
|
242
432
|
});
|
|
243
433
|
const data = await res.json();
|
|
244
434
|
clearInterval(qaTimer);
|
|
245
|
-
const
|
|
246
|
-
|
|
435
|
+
const qaElapsed = Math.round((Date.now() - qaStartTime) / 1000);
|
|
436
|
+
let sessionDocContext = { ...capturedDocContext };
|
|
247
437
|
|
|
248
438
|
if (data.ok) {
|
|
249
439
|
const borderColor = data.edited ? 'var(--green)' : 'var(--blue)';
|
|
250
440
|
const suffix = data.edited ? '\n\n\u2713 Document saved.' : '';
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
|
|
441
|
+
const answerHtml = _qaBuildAssistantHtml(data.answer + suffix, { borderColor, elapsed: qaElapsed });
|
|
442
|
+
const updatedThreadHtml = _qaMutateThreadHtml(sessionKey, tmp => {
|
|
443
|
+
const loadingEl = tmp.querySelector('#' + loadingId);
|
|
444
|
+
if (loadingEl) loadingEl.remove();
|
|
445
|
+
tmp.insertAdjacentHTML('beforeend', answerHtml);
|
|
446
|
+
});
|
|
254
447
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
448
|
+
runtime.history.push({ role: 'user', text: message });
|
|
449
|
+
runtime.history.push({ role: 'assistant', text: data.answer });
|
|
450
|
+
if (_qaIsActiveSession(sessionKey)) _qaHistory = runtime.history.slice();
|
|
258
451
|
|
|
259
|
-
// Notify sidebar page link
|
|
260
452
|
_qaNotifySidebar(capturedFilePath);
|
|
261
|
-
|
|
262
|
-
// Execute any CC actions (dispatch, note, etc.)
|
|
263
453
|
if (data.actions && data.actions.length > 0) {
|
|
264
|
-
for (const action of data.actions)
|
|
454
|
+
for (const action of data.actions) await ccExecuteAction(action);
|
|
265
455
|
}
|
|
266
456
|
|
|
267
|
-
// Refresh modal body if document was edited
|
|
268
457
|
if (data.edited && data.content) {
|
|
269
458
|
const display = data.content.replace(/^---[\s\S]*?---\n*/m, '');
|
|
270
459
|
const isJson = capturedFilePath && capturedFilePath.endsWith('.json');
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
460
|
+
sessionDocContext.content = display;
|
|
461
|
+
sessionDocContext.selection = '';
|
|
462
|
+
if (_qaIsActiveSession(sessionKey)) {
|
|
463
|
+
const body = document.getElementById('modal-body');
|
|
464
|
+
if (isJson) {
|
|
465
|
+
body.textContent = display;
|
|
466
|
+
} else {
|
|
467
|
+
body.innerHTML = renderMd(display);
|
|
468
|
+
body.style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
469
|
+
body.style.whiteSpace = 'normal';
|
|
470
|
+
}
|
|
471
|
+
_modalDocContext.content = display;
|
|
278
472
|
}
|
|
279
|
-
_modalDocContext.content = display;
|
|
280
473
|
}
|
|
474
|
+
|
|
475
|
+
_qaPersistSession(sessionKey, {
|
|
476
|
+
threadHtml: updatedThreadHtml,
|
|
477
|
+
docContext: sessionDocContext,
|
|
478
|
+
filePath: capturedFilePath,
|
|
479
|
+
history: runtime.history,
|
|
480
|
+
queue: runtime.queue,
|
|
481
|
+
});
|
|
281
482
|
} else {
|
|
282
|
-
const
|
|
283
|
-
|
|
483
|
+
const errorHtml = _qaBuildAssistantHtml('Error: ' + (data.error || 'Failed'), { color: 'var(--red)', isError: true, elapsed: qaElapsed });
|
|
484
|
+
const updatedThreadHtml = _qaMutateThreadHtml(sessionKey, tmp => {
|
|
485
|
+
const loadingEl = tmp.querySelector('#' + loadingId);
|
|
486
|
+
if (loadingEl) loadingEl.remove();
|
|
487
|
+
tmp.insertAdjacentHTML('beforeend', errorHtml);
|
|
488
|
+
});
|
|
489
|
+
_qaPersistSession(sessionKey, {
|
|
490
|
+
threadHtml: updatedThreadHtml,
|
|
491
|
+
docContext: sessionDocContext,
|
|
492
|
+
filePath: capturedFilePath,
|
|
493
|
+
history: runtime.history,
|
|
494
|
+
queue: runtime.queue,
|
|
495
|
+
});
|
|
284
496
|
}
|
|
285
497
|
} catch (e) {
|
|
286
498
|
clearInterval(qaTimer);
|
|
287
|
-
const loadingEl = document.getElementById(loadingId);
|
|
288
|
-
if (loadingEl) loadingEl.remove();
|
|
289
499
|
const qaElapsedExc = Math.round((Date.now() - qaStartTime) / 1000);
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
500
|
+
const messageHtml = e.name === 'AbortError'
|
|
501
|
+
? _qaBuildAssistantHtml('Stopped', { color: 'var(--muted)', isError: true, elapsed: qaElapsedExc })
|
|
502
|
+
: _qaBuildAssistantHtml('Error: ' + e.message, { color: 'var(--red)', isError: true, elapsed: qaElapsedExc });
|
|
503
|
+
const updatedThreadHtml = _qaMutateThreadHtml(sessionKey, tmp => {
|
|
504
|
+
const loadingEl = tmp.querySelector('#' + loadingId);
|
|
505
|
+
if (loadingEl) loadingEl.remove();
|
|
506
|
+
tmp.insertAdjacentHTML('beforeend', messageHtml);
|
|
507
|
+
});
|
|
508
|
+
_qaPersistSession(sessionKey, {
|
|
509
|
+
threadHtml: updatedThreadHtml,
|
|
510
|
+
docContext: capturedDocContext,
|
|
511
|
+
filePath: capturedFilePath,
|
|
512
|
+
history: runtime.history,
|
|
513
|
+
queue: runtime.queue,
|
|
514
|
+
});
|
|
295
515
|
}
|
|
296
516
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (_qaQueue.length === 0) {
|
|
303
|
-
const doneCard = findCardForFile(capturedFilePath);
|
|
304
|
-
if (doneCard) clearNotifBadge(doneCard);
|
|
517
|
+
runtime.processing = false;
|
|
518
|
+
runtime.abortController = null;
|
|
519
|
+
if (_qaIsActiveSession(sessionKey)) {
|
|
520
|
+
_qaProcessing = false;
|
|
521
|
+
_qaAbortController = null;
|
|
305
522
|
}
|
|
306
523
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
_qaSessions.set(_qaSessionKey, {
|
|
314
|
-
history: _qaHistory,
|
|
315
|
-
threadHtml: thread.innerHTML,
|
|
316
|
-
docContext: sessionDocContext,
|
|
317
|
-
filePath: sessionFilePath,
|
|
318
|
-
});
|
|
319
|
-
_saveQaSessions();
|
|
320
|
-
// Show notification badge on source card when modal was closed during processing
|
|
321
|
-
if (!modalIsOpen) {
|
|
322
|
-
const card = findCardForFile(sessionFilePath);
|
|
323
|
-
if (card) showNotifBadge(card, _qaQueue.length > 0 ? 'processing' : 'done');
|
|
324
|
-
_qaSessionKey = '';
|
|
524
|
+
if (runtime.queue.length === 0) {
|
|
525
|
+
const doneCard = findCardForFile(capturedFilePath);
|
|
526
|
+
if (_qaIsActiveSession(sessionKey)) {
|
|
527
|
+
if (doneCard) clearNotifBadge(doneCard);
|
|
528
|
+
} else if (doneCard) {
|
|
529
|
+
showNotifBadge(doneCard, 'done');
|
|
325
530
|
}
|
|
531
|
+
} else {
|
|
532
|
+
const pendingCard = findCardForFile(capturedFilePath);
|
|
533
|
+
if (pendingCard) showNotifBadge(pendingCard, 'processing');
|
|
326
534
|
}
|
|
327
535
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
536
|
+
if (runtime.queue.length > 0) {
|
|
537
|
+
const next = runtime.queue.shift();
|
|
538
|
+
const nextThreadHtml = _qaMutateThreadHtml(sessionKey, tmp => {
|
|
539
|
+
const queuedEl = tmp.querySelector('.qa-queued-item');
|
|
540
|
+
if (queuedEl) queuedEl.remove();
|
|
541
|
+
tmp.insertAdjacentHTML('beforeend', _qaBuildUserMessageHtml(next.message, next.selection));
|
|
542
|
+
});
|
|
543
|
+
_qaPersistSession(sessionKey, {
|
|
544
|
+
threadHtml: nextThreadHtml,
|
|
545
|
+
docContext: (_qaSessions.get(sessionKey) || {}).docContext || capturedDocContext,
|
|
546
|
+
filePath: capturedFilePath,
|
|
547
|
+
history: runtime.history,
|
|
548
|
+
queue: runtime.queue,
|
|
549
|
+
});
|
|
550
|
+
if (_qaIsActiveSession(sessionKey)) _qaQueue = _qaCloneQueue(runtime.queue);
|
|
551
|
+
_processQaMessage(next.message, next.selection, {
|
|
552
|
+
sessionKey,
|
|
553
|
+
filePath: capturedFilePath,
|
|
554
|
+
docContext: (_qaSessions.get(sessionKey) || {}).docContext || capturedDocContext,
|
|
555
|
+
});
|
|
556
|
+
} else if (_qaIsActiveSession(sessionKey)) {
|
|
557
|
+
_qaQueue = [];
|
|
337
558
|
document.getElementById('modal-qa-input')?.focus();
|
|
338
559
|
}
|
|
339
560
|
}
|
|
340
561
|
|
|
341
562
|
function qaAbort() {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
563
|
+
const runtime = _qaGetRuntime(_qaSessionKey);
|
|
564
|
+
if (runtime?.abortController) {
|
|
565
|
+
runtime.abortController.abort();
|
|
566
|
+
runtime.abortController = null;
|
|
345
567
|
}
|
|
346
|
-
|
|
347
|
-
//
|
|
568
|
+
if (_qaAbortController) _qaAbortController = null;
|
|
569
|
+
// Don't reset _qaProcessing here — the catch block in _processQaMessage handles it.
|
|
348
570
|
// Don't clear _qaQueue — queued messages auto-process after abort.
|
|
349
571
|
}
|
|
350
572
|
|
package/dashboard/js/modal.js
CHANGED
|
@@ -13,24 +13,17 @@ function closeModal() {
|
|
|
13
13
|
const resetBtn = document.getElementById('modal-settings-reset');
|
|
14
14
|
if (resetBtn) resetBtn.remove();
|
|
15
15
|
// Save Q&A session for this document (persist across modal open/close)
|
|
16
|
-
if (_qaSessionKey && (_qaHistory.length > 0 || _qaQueue.length > 0)) {
|
|
17
|
-
|
|
18
|
-
history: _qaHistory,
|
|
19
|
-
threadHtml: (document.getElementById('modal-qa-thread') || {}).innerHTML || '',
|
|
20
|
-
docContext: { ..._modalDocContext },
|
|
21
|
-
filePath: _modalFilePath,
|
|
22
|
-
});
|
|
23
|
-
_saveQaSessions();
|
|
16
|
+
if (_qaSessionKey && (_qaHistory.length > 0 || _qaQueue.length > 0 || _qaProcessing)) {
|
|
17
|
+
_qaSaveActiveSessionState();
|
|
24
18
|
}
|
|
25
19
|
// If still processing, show animated badge on the source card
|
|
26
20
|
if (_qaProcessing && _modalFilePath) {
|
|
27
21
|
const card = findCardForFile(_modalFilePath);
|
|
28
22
|
if (card) showNotifBadge(card, 'processing');
|
|
29
23
|
}
|
|
30
|
-
// Reset UI state but don't kill processing
|
|
24
|
+
// Reset active UI state but don't kill background processing — it is tracked per session
|
|
25
|
+
_qaResetActiveState();
|
|
31
26
|
_modalDocContext = { title: '', content: '', selection: '' };
|
|
32
|
-
// Keep session key alive if processing is in flight — result will save when it completes
|
|
33
|
-
if (!_qaProcessing) _qaSessionKey = '';
|
|
34
27
|
document.getElementById('modal-qa-input').value = '';
|
|
35
28
|
document.getElementById('modal-qa-input').placeholder = 'Ask about this document (or select text first)...';
|
|
36
29
|
document.getElementById('modal-qa-pill').style.display = 'none';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1027",
|
|
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"
|