@yemi33/minions 0.1.1754 → 0.1.1756
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 +10 -0
- package/dashboard/js/modal-qa.js +17 -7
- package/dashboard.js +35 -3
- package/engine/copilot-models.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1756 (2026-05-06)
|
|
4
|
+
|
|
5
|
+
### Other
|
|
6
|
+
- perf(doc-chat): cache locked answer in stream stripper, drop 20K doc truncation
|
|
7
|
+
|
|
8
|
+
## 0.1.1755 (2026-05-06)
|
|
9
|
+
|
|
10
|
+
### Fixes
|
|
11
|
+
- keep queued strips pinned below progress UX and answers
|
|
12
|
+
|
|
3
13
|
## 0.1.1754 (2026-05-06)
|
|
4
14
|
|
|
5
15
|
### Fixes
|
package/dashboard/js/modal-qa.js
CHANGED
|
@@ -30,13 +30,23 @@ const QA_QUEUE_CAP = 10; // max queued messages
|
|
|
30
30
|
const QA_STREAM_STALL_MS = 6 * 60 * 1000; // allow the full doc-chat timeout before treating heartbeat-only streams as stalled
|
|
31
31
|
let _qaSessionKey = ''; // key for current conversation (title or filePath)
|
|
32
32
|
|
|
33
|
+
// Insert html at the bottom of the thread but above any pending "Queued: ..."
|
|
34
|
+
// strips, so queued messages always remain visually below the active progress
|
|
35
|
+
// UX / answer / errors.
|
|
36
|
+
function _qaInsertBeforeQueued(container, html) {
|
|
37
|
+
if (!container) return;
|
|
38
|
+
const firstQueued = container.querySelector && container.querySelector('.qa-queued-item');
|
|
39
|
+
if (firstQueued) firstQueued.insertAdjacentHTML('beforebegin', html);
|
|
40
|
+
else container.insertAdjacentHTML('beforeend', html);
|
|
41
|
+
}
|
|
42
|
+
|
|
33
43
|
function _renderQaUserMessage(thread, message, selection) {
|
|
34
44
|
let qHtml = '<div class="modal-qa-q">' + escHtml(message);
|
|
35
45
|
if (selection) {
|
|
36
46
|
qHtml += '<span class="selection-ref">Re: "' + escHtml(selection.slice(0, 100)) + ((selection.length > 100) ? '...' : '') + '"</span>';
|
|
37
47
|
}
|
|
38
48
|
qHtml += '</div>';
|
|
39
|
-
thread
|
|
49
|
+
_qaInsertBeforeQueued(thread, qHtml);
|
|
40
50
|
thread.scrollTop = thread.scrollHeight;
|
|
41
51
|
_showThreadWrap();
|
|
42
52
|
}
|
|
@@ -442,7 +452,7 @@ async function _processQaMessage(message, selection, opts) {
|
|
|
442
452
|
const loadingId = 'chat-loading-' + Date.now();
|
|
443
453
|
const loadingHtml = _qaBuildLoadingHtml(loadingId, runtime.queue.length);
|
|
444
454
|
const startThreadHtml = _qaMutateThreadHtml(sessionKey, tmp => {
|
|
445
|
-
tmp
|
|
455
|
+
_qaInsertBeforeQueued(tmp, loadingHtml);
|
|
446
456
|
});
|
|
447
457
|
_qaPersistSession(sessionKey, {
|
|
448
458
|
threadHtml: startThreadHtml,
|
|
@@ -606,8 +616,8 @@ async function _processQaMessage(message, selection, opts) {
|
|
|
606
616
|
let updatedThreadHtml = _qaMutateThreadHtml(sessionKey, tmp => {
|
|
607
617
|
const loadingEl = tmp.querySelector('#' + loadingId);
|
|
608
618
|
if (loadingEl) loadingEl.remove();
|
|
609
|
-
tmp
|
|
610
|
-
if (rawErrorHtml) tmp
|
|
619
|
+
_qaInsertBeforeQueued(tmp, answerHtml);
|
|
620
|
+
if (rawErrorHtml) _qaInsertBeforeQueued(tmp, rawErrorHtml);
|
|
611
621
|
});
|
|
612
622
|
|
|
613
623
|
runtime.history.push({ role: 'user', text: message });
|
|
@@ -621,7 +631,7 @@ async function _processQaMessage(message, selection, opts) {
|
|
|
621
631
|
} else if (evt.actionParseError) {
|
|
622
632
|
const warning = '<div class="modal-qa-a" style="color:var(--red)">Actions block emitted but JSON could not be parsed — no actions were executed. Resend or rephrase. (' + escHtml(String(evt.actionParseError).slice(0, 200)) + ')</div>';
|
|
623
633
|
updatedThreadHtml = _qaMutateThreadHtml(sessionKey, tmp => {
|
|
624
|
-
tmp
|
|
634
|
+
_qaInsertBeforeQueued(tmp, warning);
|
|
625
635
|
});
|
|
626
636
|
}
|
|
627
637
|
|
|
@@ -698,7 +708,7 @@ async function _processQaMessage(message, selection, opts) {
|
|
|
698
708
|
const updatedThreadHtml = _qaMutateThreadHtml(sessionKey, tmp => {
|
|
699
709
|
const loadingEl = tmp.querySelector('#' + loadingId);
|
|
700
710
|
if (loadingEl) loadingEl.remove();
|
|
701
|
-
tmp
|
|
711
|
+
_qaInsertBeforeQueued(tmp, messageHtml);
|
|
702
712
|
});
|
|
703
713
|
_qaPersistSession(sessionKey, {
|
|
704
714
|
threadHtml: updatedThreadHtml,
|
|
@@ -733,7 +743,7 @@ async function _processQaMessage(message, selection, opts) {
|
|
|
733
743
|
const nextThreadHtml = _qaMutateThreadHtml(sessionKey, tmp => {
|
|
734
744
|
const queuedEl = tmp.querySelector('.qa-queued-item');
|
|
735
745
|
if (queuedEl) queuedEl.remove();
|
|
736
|
-
tmp
|
|
746
|
+
_qaInsertBeforeQueued(tmp, _qaBuildUserMessageHtml(next.message, next.selection));
|
|
737
747
|
});
|
|
738
748
|
_qaPersistSession(sessionKey, {
|
|
739
749
|
threadHtml: nextThreadHtml,
|
package/dashboard.js
CHANGED
|
@@ -2653,10 +2653,40 @@ function _recoverPartialDocChatResponse(result, sessionKey) {
|
|
|
2653
2653
|
}
|
|
2654
2654
|
|
|
2655
2655
|
|
|
2656
|
+
// Wraps the streaming onChunk so that once the document delimiter is observed
|
|
2657
|
+
// in the growing text, subsequent chunks reuse the locked answer instead of
|
|
2658
|
+
// re-scanning the tail. The model emits "<explanation> ---DOCUMENT--- <full file>"
|
|
2659
|
+
// — the answer portion can't grow after the delimiter, so re-parsing every
|
|
2660
|
+
// chunk through the regenerated file body is wasted O(n²) work.
|
|
2661
|
+
//
|
|
2662
|
+
// Also dedups identical post-strip answers: the upstream accumulator dedups
|
|
2663
|
+
// against the raw growing text, but that text keeps changing as the document
|
|
2664
|
+
// body streams in even though the visible answer is locked. Without this
|
|
2665
|
+
// guard the SSE writer fires a duplicate `chunk` event for every doc-body
|
|
2666
|
+
// delta, which triggers a client DOM rerender and localStorage write each time.
|
|
2667
|
+
function _makeDocChatStreamStripper(onChunk) {
|
|
2668
|
+
if (!onChunk) return undefined;
|
|
2669
|
+
let lockedAnswer = null;
|
|
2670
|
+
let lastSent;
|
|
2671
|
+
return (text) => {
|
|
2672
|
+
let answer;
|
|
2673
|
+
if (lockedAnswer !== null) {
|
|
2674
|
+
answer = lockedAnswer;
|
|
2675
|
+
} else {
|
|
2676
|
+
const parsed = _parseDocChatResultText(text);
|
|
2677
|
+
if (parsed.content !== null) lockedAnswer = parsed.answer;
|
|
2678
|
+
answer = parsed.answer;
|
|
2679
|
+
}
|
|
2680
|
+
if (answer === lastSent) return;
|
|
2681
|
+
lastSent = answer;
|
|
2682
|
+
onChunk(answer);
|
|
2683
|
+
};
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2656
2686
|
// Doc-specific wrapper — adds document context, parses ---DOCUMENT---
|
|
2657
2687
|
async function ccDocCall({ message, document, title, filePath, selection, canEdit, isJson, model, freshSession, onAbortReady }) {
|
|
2658
2688
|
const sessionKey = filePath || title;
|
|
2659
|
-
const docSlice = document
|
|
2689
|
+
const docSlice = String(document || '');
|
|
2660
2690
|
|
|
2661
2691
|
// freshSession: true → discard any prior session for this key so the call starts clean.
|
|
2662
2692
|
// Used by one-shot generation flows (e.g. Create Plan from meeting) that must not
|
|
@@ -2720,7 +2750,8 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
|
|
|
2720
2750
|
|
|
2721
2751
|
async function ccDocCallStreaming({ message, document, title, filePath, selection, canEdit, isJson, model, freshSession, onAbortReady, onChunk, onToolUse, onRetry }) {
|
|
2722
2752
|
const sessionKey = filePath || title;
|
|
2723
|
-
const docSlice = document
|
|
2753
|
+
const docSlice = String(document || '');
|
|
2754
|
+
const streamStripper = _makeDocChatStreamStripper(onChunk);
|
|
2724
2755
|
|
|
2725
2756
|
if (freshSession && sessionKey) {
|
|
2726
2757
|
docSessions.delete(sessionKey);
|
|
@@ -2742,7 +2773,7 @@ async function ccDocCallStreaming({ message, document, title, filePath, selectio
|
|
|
2742
2773
|
systemPrompt: DOC_CHAT_SYSTEM_PROMPT,
|
|
2743
2774
|
...(model ? { model } : {}),
|
|
2744
2775
|
onAbortReady,
|
|
2745
|
-
onChunk:
|
|
2776
|
+
onChunk: streamStripper,
|
|
2746
2777
|
onToolUse,
|
|
2747
2778
|
onRetry,
|
|
2748
2779
|
});
|
|
@@ -7464,6 +7495,7 @@ module.exports = {
|
|
|
7464
7495
|
parsePinnedEntries,
|
|
7465
7496
|
_parseDocChatResultText,
|
|
7466
7497
|
_formatDocChatContext,
|
|
7498
|
+
_makeDocChatStreamStripper,
|
|
7467
7499
|
_docChatErrorMessage,
|
|
7468
7500
|
_docChatPartialWarning,
|
|
7469
7501
|
_docChatFailureResponse,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1756",
|
|
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"
|