@yemi33/minions 0.1.2410 → 0.1.2411
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/dashboard/js/settings.js +137 -13
- package/dashboard/styles.css +27 -0
- package/docs/pr-screenshots/pr-916/model-picker-AFTER-crop.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-BEFORE-crop.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-dropdown-AFTER.png +0 -0
- package/engine/runtimes/copilot.js +54 -8
- package/package.json +1 -1
package/dashboard/js/settings.js
CHANGED
|
@@ -144,7 +144,7 @@ async function openSettings() {
|
|
|
144
144
|
'</div>' +
|
|
145
145
|
'<div data-search="default model fleet">' +
|
|
146
146
|
'<label style="font-size:var(--text-sm);color:var(--muted);display:block;margin-bottom:2px">Default Model</label>' +
|
|
147
|
-
'<div id="set-defaultModel-wrap"><input id="set-defaultModel" value="' + escHtml(e.defaultModel || '') + '" placeholder="Default (CLI chooses)" style="
|
|
147
|
+
'<div id="set-defaultModel-wrap"><input id="set-defaultModel" value="' + escHtml(e.defaultModel || '') + '" placeholder="Default (CLI chooses)" style="' + MODEL_FIELD_INPUT_STYLE + '"></div>' +
|
|
148
148
|
'<div style="font-size:var(--text-xs);color:var(--muted);margin-top:1px">Empty = let the runtime pick its own default</div>' +
|
|
149
149
|
'</div>' +
|
|
150
150
|
'</div>' +
|
|
@@ -168,7 +168,7 @@ async function openSettings() {
|
|
|
168
168
|
// swaps THIS element. Without the wrap it would wipe the label
|
|
169
169
|
// and the hint below, breaking vertical alignment with the
|
|
170
170
|
// sibling CC CLI / Effort columns.
|
|
171
|
-
'<div id="set-ccModel-wrap"><input id="set-ccModel" value="' + escHtml(e.ccModel || '') + '" placeholder="(inherits Default Model)" style="
|
|
171
|
+
'<div id="set-ccModel-wrap"><input id="set-ccModel" value="' + escHtml(e.ccModel || '') + '" placeholder="(inherits Default Model)" style="' + MODEL_FIELD_INPUT_STYLE + '"></div>' +
|
|
172
172
|
'<div style="font-size:var(--text-xs);color:var(--muted);margin-top:1px">Empty = inherit Default Model</div>' +
|
|
173
173
|
'</div>' +
|
|
174
174
|
'<div data-search="cc effort reasoning">' +
|
|
@@ -995,6 +995,133 @@ async function initRuntimeFleetUI(engineCfg, agentsCfg) {
|
|
|
995
995
|
});
|
|
996
996
|
}
|
|
997
997
|
|
|
998
|
+
// ── Model field styling (W-mrvdup4h000fc0a2) ─────────────────────────────
|
|
999
|
+
// Default Model / CC Model in the Runtime pane are datalist-backed <input>
|
|
1000
|
+
// comboboxes (staged/custom model IDs must stay typeable, so they can't be
|
|
1001
|
+
// closed native <select>s). These shared styles make the input match the box
|
|
1002
|
+
// metrics of the sibling native <select> dropdowns (Default CLI / CC CLI /
|
|
1003
|
+
// Effort / Font Size) and add a chevron so it reads as a dropdown.
|
|
1004
|
+
// box-sizing:border-box keeps the bordered box the same rendered size as a
|
|
1005
|
+
// width:100% <select>; the chevron is drawn just inside the input's right edge.
|
|
1006
|
+
const MODEL_FIELD_INPUT_BOX = 'box-sizing:border-box;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:var(--text-md)';
|
|
1007
|
+
const MODEL_FIELD_CHEVRON = "background-image:url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%2210%22%20height=%226%22%3E%3Cpath%20d=%22M1%201l4%204%204-4%22%20fill=%22none%22%20stroke=%22%238b949e%22%20stroke-width=%221.5%22/%3E%3C/svg%3E');background-repeat:no-repeat;background-position:right 8px center;padding-right:22px";
|
|
1008
|
+
// Full-width variant for the initial (pre-discovery) placeholder inputs.
|
|
1009
|
+
const MODEL_FIELD_INPUT_STYLE = 'width:100%;' + MODEL_FIELD_INPUT_BOX + ';' + MODEL_FIELD_CHEVRON;
|
|
1010
|
+
|
|
1011
|
+
// ── Model-picker dropdown (W-mrvdup4h000fc0a2 / PR #916) ──────────────────
|
|
1012
|
+
// The Default Model / CC Model fields must stay editable <input>s so staged
|
|
1013
|
+
// and custom model IDs remain typeable — they can't be closed native
|
|
1014
|
+
// <select>s. Discovery used to be surfaced through a browser <datalist>, but
|
|
1015
|
+
// the datalist autocomplete popup is browser chrome and reads visibly
|
|
1016
|
+
// different from the native <select> dropdowns of the sibling controls
|
|
1017
|
+
// (Default CLI / CC CLI / Effort) — the mismatch the reviewer flagged: the
|
|
1018
|
+
// "dialog that it launches doesn't match". This wires a dashboard-themed
|
|
1019
|
+
// popover (.model-combo-popup) onto the input so the launched suggestion list
|
|
1020
|
+
// matches those dropdowns while keeping the field free-typeable.
|
|
1021
|
+
//
|
|
1022
|
+
// inputEl : the editable <input> (kept typeable; free text is preserved)
|
|
1023
|
+
// popupEl : the (initially empty) .model-combo-popup container to render into
|
|
1024
|
+
// models : array of { id, name } discovery results (may be empty)
|
|
1025
|
+
//
|
|
1026
|
+
// The popup is positioned with position:fixed at open time (see the CSS) so it
|
|
1027
|
+
// escapes the .settings-content / .settings-body overflow clipping and floats
|
|
1028
|
+
// like a native dropdown. All listeners are element-scoped so they are GC'd
|
|
1029
|
+
// with the input when loadModelsForRuntime re-renders (no global leak).
|
|
1030
|
+
function _wireModelCombobox(inputEl, popupEl, models) {
|
|
1031
|
+
if (!inputEl || !popupEl) return;
|
|
1032
|
+
const options = (models || [])
|
|
1033
|
+
.map(function(m) {
|
|
1034
|
+
const id = m.id || m.name || '';
|
|
1035
|
+
const label = (m.name && m.name !== id) ? m.name : '';
|
|
1036
|
+
return { id: id, label: label };
|
|
1037
|
+
})
|
|
1038
|
+
.filter(function(o) { return o.id; });
|
|
1039
|
+
if (options.length === 0) return;
|
|
1040
|
+
let visible = [];
|
|
1041
|
+
let activeIdx = -1;
|
|
1042
|
+
|
|
1043
|
+
function position() {
|
|
1044
|
+
const r = inputEl.getBoundingClientRect();
|
|
1045
|
+
popupEl.style.left = r.left + 'px';
|
|
1046
|
+
popupEl.style.top = r.bottom + 'px';
|
|
1047
|
+
popupEl.style.width = r.width + 'px';
|
|
1048
|
+
}
|
|
1049
|
+
function render() {
|
|
1050
|
+
const q = (inputEl.value || '').toLowerCase();
|
|
1051
|
+
// A value that exactly equals an option shows the full list, so re-focusing
|
|
1052
|
+
// a filled field still offers every model (matching native <select>).
|
|
1053
|
+
const exact = options.some(function(o) { return o.id.toLowerCase() === q; });
|
|
1054
|
+
visible = (!q || exact) ? options.slice()
|
|
1055
|
+
: options.filter(function(o) {
|
|
1056
|
+
return o.id.toLowerCase().indexOf(q) !== -1 || (o.label && o.label.toLowerCase().indexOf(q) !== -1);
|
|
1057
|
+
});
|
|
1058
|
+
if (visible.length === 0) { hide(); return; }
|
|
1059
|
+
if (activeIdx >= visible.length) activeIdx = -1;
|
|
1060
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: model id, model label)
|
|
1061
|
+
popupEl.innerHTML = visible.map(function(o, i) {
|
|
1062
|
+
return '<div class="model-combo-option' + (i === activeIdx ? ' active' : '') + '" data-idx="' + i + '">' +
|
|
1063
|
+
'<span class="model-combo-id">' + escHtml(o.id) + '</span>' +
|
|
1064
|
+
(o.label ? '<span class="model-combo-label">' + escHtml(o.label) + '</span>' : '') +
|
|
1065
|
+
'</div>';
|
|
1066
|
+
}).join('');
|
|
1067
|
+
position();
|
|
1068
|
+
popupEl.classList.add('visible');
|
|
1069
|
+
inputEl.setAttribute('aria-expanded', 'true');
|
|
1070
|
+
}
|
|
1071
|
+
function hide() {
|
|
1072
|
+
popupEl.classList.remove('visible');
|
|
1073
|
+
inputEl.setAttribute('aria-expanded', 'false');
|
|
1074
|
+
activeIdx = -1;
|
|
1075
|
+
}
|
|
1076
|
+
function pick(o) {
|
|
1077
|
+
if (!o) return;
|
|
1078
|
+
inputEl.value = o.id;
|
|
1079
|
+
hide();
|
|
1080
|
+
}
|
|
1081
|
+
function scrollActive() {
|
|
1082
|
+
const el = popupEl.querySelector('.model-combo-option.active');
|
|
1083
|
+
if (el && el.scrollIntoView) el.scrollIntoView({ block: 'nearest' });
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
inputEl.addEventListener('focus', render);
|
|
1087
|
+
// A click on an already-focused input does not re-fire `focus`, so after a
|
|
1088
|
+
// selection (pick → hide) or Escape (hide) the still-focused input could not
|
|
1089
|
+
// be reopened by clicking — it only stays a dead field until blur+refocus.
|
|
1090
|
+
// Reopen the popup on click whenever it is currently hidden (matches how a
|
|
1091
|
+
// native <select> reopens on click). No-op on the focus-triggering click,
|
|
1092
|
+
// which already opened it, so there is no open/close flicker.
|
|
1093
|
+
inputEl.addEventListener('click', function() {
|
|
1094
|
+
if (!popupEl.classList.contains('visible')) render();
|
|
1095
|
+
});
|
|
1096
|
+
inputEl.addEventListener('input', function() { activeIdx = -1; render(); });
|
|
1097
|
+
inputEl.addEventListener('keydown', function(ev) {
|
|
1098
|
+
const open = popupEl.classList.contains('visible');
|
|
1099
|
+
if (ev.key === 'ArrowDown') {
|
|
1100
|
+
ev.preventDefault();
|
|
1101
|
+
if (!open) { render(); return; }
|
|
1102
|
+
activeIdx = Math.min(activeIdx + 1, visible.length - 1);
|
|
1103
|
+
render(); scrollActive();
|
|
1104
|
+
} else if (ev.key === 'ArrowUp') {
|
|
1105
|
+
if (!open) return;
|
|
1106
|
+
ev.preventDefault();
|
|
1107
|
+
activeIdx = Math.max(activeIdx - 1, 0);
|
|
1108
|
+
render(); scrollActive();
|
|
1109
|
+
} else if (ev.key === 'Enter') {
|
|
1110
|
+
if (open && activeIdx >= 0) { ev.preventDefault(); pick(visible[activeIdx]); }
|
|
1111
|
+
} else if (ev.key === 'Escape') {
|
|
1112
|
+
if (open) { ev.stopPropagation(); hide(); }
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
// mousedown (not click) so the pick fires before the input's blur hides it.
|
|
1116
|
+
popupEl.addEventListener('mousedown', function(ev) {
|
|
1117
|
+
const row = ev.target.closest('.model-combo-option');
|
|
1118
|
+
if (!row) return;
|
|
1119
|
+
ev.preventDefault();
|
|
1120
|
+
pick(visible[Number(row.getAttribute('data-idx'))]);
|
|
1121
|
+
});
|
|
1122
|
+
inputEl.addEventListener('blur', function() { setTimeout(hide, 120); });
|
|
1123
|
+
}
|
|
1124
|
+
|
|
998
1125
|
/**
|
|
999
1126
|
* Render an editable model field backed by discovered-model suggestions.
|
|
1000
1127
|
* Discovery is advisory: staged/future/custom model IDs must remain typeable.
|
|
@@ -1005,7 +1132,7 @@ async function loadModelsForRuntime(runtimeName, inputId, currentValue, forceRef
|
|
|
1005
1132
|
const token = _nextModelLoadToken('runtime', inputId);
|
|
1006
1133
|
if (!runtimeName) {
|
|
1007
1134
|
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: currentValue; inputId is an internal fixed DOM id)
|
|
1008
|
-
wrap.innerHTML = '<input id="' + inputId + '" value="' + escHtml(currentValue || '') + '" placeholder="(no runtime selected)" disabled style="
|
|
1135
|
+
wrap.innerHTML = '<input id="' + inputId + '" value="' + escHtml(currentValue || '') + '" placeholder="(no runtime selected)" disabled style="' + MODEL_FIELD_INPUT_STYLE + ';color:var(--muted)">';
|
|
1009
1136
|
return;
|
|
1010
1137
|
}
|
|
1011
1138
|
let payload = { models: null };
|
|
@@ -1013,17 +1140,14 @@ async function loadModelsForRuntime(runtimeName, inputId, currentValue, forceRef
|
|
|
1013
1140
|
|
|
1014
1141
|
if (!_isCurrentModelLoad('runtime', inputId, token)) return;
|
|
1015
1142
|
const models = Array.isArray(payload.models) ? payload.models : null;
|
|
1016
|
-
const listId = inputId + '-options';
|
|
1017
|
-
let opts = '';
|
|
1018
|
-
for (const m of (models || [])) {
|
|
1019
|
-
const id = m.id || m.name || '';
|
|
1020
|
-
if (!id) continue;
|
|
1021
|
-
const label = m.name && m.name !== id ? m.name : '';
|
|
1022
|
-
opts += '<option value="' + escHtml(id) + '"' + (label ? ' label="' + escHtml(label) + '"' : '') + '></option>';
|
|
1023
|
-
}
|
|
1024
1143
|
const title = forceRefresh ? 'Model list refreshed' : 'Refresh models from the selected CLI';
|
|
1025
|
-
//
|
|
1026
|
-
|
|
1144
|
+
// Editable model input + themed suggestion popover (see _wireModelCombobox).
|
|
1145
|
+
// The field stays free-typeable for staged/custom IDs; discovered models are
|
|
1146
|
+
// offered through .model-combo-popup so the launched list matches the sibling
|
|
1147
|
+
// native <select> dropdowns instead of the browser's <datalist> chrome.
|
|
1148
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: currentValue, title; inputId is an internal fixed DOM id)
|
|
1149
|
+
wrap.innerHTML = '<div class="model-combo" style="width:100%"><div style="display:flex;gap:4px;width:100%"><input id="' + inputId + '" value="' + escHtml(currentValue || '') + '" placeholder="Default (CLI chooses)" autocomplete="off" role="combobox" aria-autocomplete="list" aria-expanded="false" style="min-width:0;flex:1;' + MODEL_FIELD_INPUT_BOX + ';' + MODEL_FIELD_CHEVRON + '"><button type="button" data-model-refresh title="' + escHtml(title) + '" aria-label="Refresh model list" style="padding:3px 8px;background:var(--surface2);border:1px solid var(--border);border-radius:4px;color:var(--muted);cursor:pointer">↻</button></div><div class="model-combo-popup" data-model-popup></div></div>';
|
|
1150
|
+
_wireModelCombobox(document.getElementById(inputId), wrap.querySelector('[data-model-popup]'), models || []);
|
|
1027
1151
|
const refresh = wrap.querySelector('[data-model-refresh]');
|
|
1028
1152
|
if (refresh) {
|
|
1029
1153
|
refresh.addEventListener('click', async function() {
|
package/dashboard/styles.css
CHANGED
|
@@ -889,6 +889,33 @@
|
|
|
889
889
|
.cmd-mention-item .mention-name { font-weight: 600; color: var(--purple); }
|
|
890
890
|
.cmd-mention-item .mention-role { color: var(--muted); font-size: var(--text-base); }
|
|
891
891
|
|
|
892
|
+
/* Model-picker combobox (Settings → Agents Runtime & Models, PR #916).
|
|
893
|
+
The Default Model / CC Model fields stay editable <input>s (staged/custom
|
|
894
|
+
model IDs must remain typeable) but the launched suggestion list is a
|
|
895
|
+
dashboard-themed popover instead of the browser's native <datalist>
|
|
896
|
+
chrome, so it matches the sibling native <select> dropdowns. Positioned
|
|
897
|
+
with position:fixed (coords set in JS at open time) so it escapes the
|
|
898
|
+
.settings-content / .settings-body overflow clipping and floats like a
|
|
899
|
+
native dropdown. z-index sits above modal content (.modal-bg is 400). */
|
|
900
|
+
.model-combo { position: relative; }
|
|
901
|
+
.model-combo-popup {
|
|
902
|
+
display: none; position: fixed; margin-top: 2px;
|
|
903
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: 4px;
|
|
904
|
+
padding: 2px 0; box-shadow: var(--shadow-lg); z-index: 500;
|
|
905
|
+
max-height: 260px; overflow-y: auto;
|
|
906
|
+
scrollbar-width: thin; scrollbar-color: var(--border) transparent;
|
|
907
|
+
}
|
|
908
|
+
.model-combo-popup.visible { display: block; }
|
|
909
|
+
.model-combo-popup::-webkit-scrollbar { width: 4px; }
|
|
910
|
+
.model-combo-popup::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
|
|
911
|
+
.model-combo-option {
|
|
912
|
+
padding: 4px 8px; font-size: var(--text-md); color: var(--text); cursor: pointer;
|
|
913
|
+
display: flex; align-items: baseline; gap: 6px; white-space: nowrap;
|
|
914
|
+
}
|
|
915
|
+
.model-combo-option:hover { background: var(--surface2); }
|
|
916
|
+
.model-combo-option.active { background: rgba(88,166,255,0.15); }
|
|
917
|
+
.model-combo-label { color: var(--muted); font-size: var(--text-xs); }
|
|
918
|
+
|
|
892
919
|
/* Hints bar */
|
|
893
920
|
.cmd-hints {
|
|
894
921
|
display: flex; gap: var(--space-6); margin-top: var(--space-2); font-size: var(--text-sm); color: var(--muted);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -767,10 +767,17 @@ const KNOWN_EVENT_TYPES = new Set([
|
|
|
767
767
|
* Returns { text, usage, sessionId, model } — same shape as the Claude adapter
|
|
768
768
|
* so engine/lifecycle.js can consume both transparently.
|
|
769
769
|
*
|
|
770
|
-
* - text:
|
|
771
|
-
* values
|
|
772
|
-
*
|
|
773
|
-
*
|
|
770
|
+
* - text: the TRAILING text segment of the turn — answer
|
|
771
|
+
* `assistant.message.data.content` values plus trailing deltas,
|
|
772
|
+
* but only those that arrived AFTER the last real (non-
|
|
773
|
+
* `task_complete`) tool boundary. Narration attached to tool
|
|
774
|
+
* requests is progress text and is excluded from the final
|
|
775
|
+
* answer, and a real tool call resets the accumulators so pre-
|
|
776
|
+
* tool text never welds onto the post-tool answer. This mirrors
|
|
777
|
+
* the stream consumer's `_resetTrailingSegment` and Claude's
|
|
778
|
+
* result-carries-last-message shape, so the terminal
|
|
779
|
+
* reconciliation in engine/llm.js#finalize agrees with the live
|
|
780
|
+
* stream instead of re-welding split blocks (W-mrw87290000b2965).
|
|
774
781
|
* - usage: mapped from the terminal `result` event. Copilot doesn't
|
|
775
782
|
* report cost/tokens — those fields are NULL, not 0, so the
|
|
776
783
|
* dashboard can distinguish "Copilot didn't tell us" from
|
|
@@ -809,10 +816,15 @@ function parseOutput(raw, { maxTextLength = 0 } = {}) {
|
|
|
809
816
|
const content = obj.data?.content;
|
|
810
817
|
const toolRequests = obj.data?.toolRequests;
|
|
811
818
|
const hasToolRequests = Array.isArray(toolRequests) && toolRequests.length > 0;
|
|
819
|
+
let sawRealTool = false;
|
|
812
820
|
if (hasToolRequests) {
|
|
813
821
|
for (const tr of toolRequests) {
|
|
814
|
-
|
|
815
|
-
|
|
822
|
+
if (tr?.name === 'task_complete') {
|
|
823
|
+
const summary = tr.arguments?.summary || tr.intentionSummary;
|
|
824
|
+
if (typeof summary === 'string' && summary) taskCompleteSummary = summary;
|
|
825
|
+
} else if (tr?.name) {
|
|
826
|
+
sawRealTool = true;
|
|
827
|
+
}
|
|
816
828
|
}
|
|
817
829
|
}
|
|
818
830
|
if (typeof content === 'string') {
|
|
@@ -821,9 +833,32 @@ function parseOutput(raw, { maxTextLength = 0 } = {}) {
|
|
|
821
833
|
}
|
|
822
834
|
const ot = obj.data?.outputTokens;
|
|
823
835
|
if (typeof ot === 'number') outputTokensTotal += ot;
|
|
836
|
+
// A real (non-task_complete) tool call closes the trailing text segment:
|
|
837
|
+
// the final answer is whatever text arrives AFTER it. Reset the trailing
|
|
838
|
+
// accumulators so pre-tool narration — whether it arrived as a finalized
|
|
839
|
+
// assistant.message or as raw deltas — does not weld onto the post-tool
|
|
840
|
+
// answer. Without this, `text = messageContents.join('') + pendingDeltaContent`
|
|
841
|
+
// re-welds `<pre-tool><post-tool>` in the terminal reconciliation
|
|
842
|
+
// (engine/llm.js#finalize -> runtime.parseOutput), and the CC SSE `done`
|
|
843
|
+
// payload re-introduces the weld the stream consumer already split apart
|
|
844
|
+
// (W-mrw87290000b2965). Mirrors the stream consumer's _resetTrailingSegment
|
|
845
|
+
// and Claude, whose result event carries only the last assistant message.
|
|
846
|
+
if (sawRealTool) {
|
|
847
|
+
messageContents.length = 0;
|
|
848
|
+
pendingDeltaContent = '';
|
|
849
|
+
}
|
|
824
850
|
} else if (type === 'tool.execution_start') {
|
|
825
|
-
const
|
|
826
|
-
if (
|
|
851
|
+
const toolName = obj.data?.toolName;
|
|
852
|
+
if (toolName === 'task_complete') {
|
|
853
|
+
const summary = obj.data?.arguments?.summary;
|
|
854
|
+
if (typeof summary === 'string' && summary) taskCompleteSummary = summary;
|
|
855
|
+
} else if (typeof toolName === 'string' && toolName) {
|
|
856
|
+
// Same real-tool-boundary reset as the toolRequests branch above —
|
|
857
|
+
// `tool.execution_start` is the standalone event shape (delta -> tool
|
|
858
|
+
// -> delta) that produced the original weld repro.
|
|
859
|
+
messageContents.length = 0;
|
|
860
|
+
pendingDeltaContent = '';
|
|
861
|
+
}
|
|
827
862
|
} else if (type === 'session.task_complete') {
|
|
828
863
|
const summary = obj.data?.summary;
|
|
829
864
|
if (typeof summary === 'string' && summary) taskCompleteSummary = summary;
|
|
@@ -1196,6 +1231,17 @@ function createStreamConsumer(ctx) {
|
|
|
1196
1231
|
function _resetTrailingSegment() {
|
|
1197
1232
|
trailingText = '';
|
|
1198
1233
|
_hadPriorTrailingMessage = false;
|
|
1234
|
+
// A real tool call closes the trailing text segment, so the live
|
|
1235
|
+
// `assistant.message_delta` accumulator must reset too — otherwise, when
|
|
1236
|
+
// the pre-tool text arrived ONLY as deltas (no finalizing non-tool
|
|
1237
|
+
// `assistant.message` to zero the buffer, e.g. delta(s) → tool.execution_start
|
|
1238
|
+
// → delta(s)), the post-tool deltas append onto the pre-tool text and
|
|
1239
|
+
// `ctx.pushText` emits the welded whole-turn buffer under the new (post-tool)
|
|
1240
|
+
// segmentId. The dashboard then renders the post-tool block as
|
|
1241
|
+
// `<pre-tool>.<post-tool>` with no separator (W-mrw87290 — CC weld across a
|
|
1242
|
+
// tool call). Claude's adapter resets its accumulator on tool_use for the
|
|
1243
|
+
// same reason (engine/runtimes/claude.js).
|
|
1244
|
+
copilotMessageBuffer = '';
|
|
1199
1245
|
}
|
|
1200
1246
|
|
|
1201
1247
|
function _captureTaskComplete(summary, success = true, { clearBuffer = false } = {}) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2411",
|
|
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"
|