@yemi33/minions 0.1.2129 → 0.1.2131

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.
@@ -136,7 +136,11 @@ function _ccMergeStreamText(prev, incoming) {
136
136
  if (next === current) return current;
137
137
  if (next.startsWith(current)) return next;
138
138
  if (current.startsWith(next)) return current;
139
- for (var overlap = Math.min(current.length, next.length); overlap > 0; overlap--) {
139
+ // Overlap >= 3 only a 1- or 2-char tail/head match is statistically a
140
+ // false positive (e.g. trailing 's' or '.' against a leading capital
141
+ // letter) and welds back-to-back assistant messages into `...prev.next...`
142
+ // with no separator (W-mq1jwwqo000f20d9 — CC chunk welding fix).
143
+ for (var overlap = Math.min(current.length, next.length); overlap >= 3; overlap--) {
140
144
  if (current.slice(-overlap) === next.slice(0, overlap)) {
141
145
  return current + next.slice(overlap);
142
146
  }
@@ -150,7 +150,7 @@ const RENDER_VERSIONS = {
150
150
  skills: 1,
151
151
  mcpServers: 1,
152
152
  schedules: 1,
153
- watches: 1,
153
+ watches: 2,
154
154
  meetings: 1,
155
155
  pipelines: 1,
156
156
  pinned: 1,
@@ -101,6 +101,36 @@ function _targetTypeLabel(type) {
101
101
  return _WATCH_TARGET_LABELS[type] || (type || '');
102
102
  }
103
103
 
104
+ // W-mq1j5f9z00030b8f — `teams-channel` target is `{teamId, channelId}` (object);
105
+ // other targetTypes use strings. Avoid `String(target)` → "[object Object]";
106
+ // route all target rendering here so future object-target plugins inherit safety.
107
+ function _formatWatchTarget(target, targetType) {
108
+ if (target == null || target === '') return '';
109
+ if (typeof target === 'string') return target;
110
+ if (typeof target !== 'object') return String(target);
111
+ if (targetType === 'teams-channel'
112
+ && typeof target.teamId === 'string' && target.teamId
113
+ && typeof target.channelId === 'string' && target.channelId) {
114
+ var team = target.teamId.slice(0, 8) + '…';
115
+ var chPart = target.channelId.split(':')[1] || target.channelId;
116
+ var ch = chPart.slice(0, 12) + '…';
117
+ return 'Team ' + team + ' > Channel ' + ch;
118
+ }
119
+ var json;
120
+ try { json = JSON.stringify(target); } catch (_) { return '[unserializable target]'; }
121
+ if (json == null) return '';
122
+ return json.length > 60 ? json.slice(0, 57) + '…' : json;
123
+ }
124
+
125
+ // W-mq1j5f9z00030b8f — title fallback chain: title → description → formatted target → "(no target)".
126
+ function _watchTitleFallback(w) {
127
+ if (!w) return '(no target)';
128
+ if (w.title) return w.title;
129
+ if (w.description) return w.description;
130
+ var formatted = _formatWatchTarget(w.target, w.targetType);
131
+ return formatted || '(no target)';
132
+ }
133
+
104
134
  function _intervalToHuman(ms) {
105
135
  if (!ms) return 'default';
106
136
  const sec = Math.floor(ms / 1000);
@@ -170,7 +200,7 @@ function renderWatches(watchesData, opts) {
170
200
 
171
201
  html += '<tr style="cursor:pointer" onclick="if(shouldIgnoreSelectionClick(event))return;openWatchDetail(\'' + escHtml(w.id) + '\')">' +
172
202
  '<td><span class="pr-id">' + escHtml(w.id) + '</span></td>' +
173
- '<td style="max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml(w.description || w.target) + '">' + escHtml(w.target) + '</td>' +
203
+ '<td style="max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml(_watchTitleFallback(w)) + '">' + escHtml(_formatWatchTarget(w.target, w.targetType) || _watchTitleFallback(w)) + '</td>' +
174
204
  '<td><span class="dispatch-type explore">' + escHtml(targetLabel) + '</span></td>' +
175
205
  '<td><span style="font-size:var(--text-base);color:var(--blue)">' + escHtml(condLabel) + '</span></td>' +
176
206
  '<td><span style="font-size:var(--text-sm)">' + actionLabel + '</span></td>' +
@@ -250,7 +280,7 @@ function _renderWatchRequiresDetail(requires) {
250
280
  var rows = requires.map(function(r, i) {
251
281
  return '<div style="margin-top:4px;padding:6px 8px;background:var(--surface2);border-radius:4px;font-size:var(--text-base)">' +
252
282
  '<span style="color:var(--muted)">[' + (i + 1) + ']</span> ' +
253
- escHtml(r && r.target ? r.target : '?') +
283
+ escHtml(r && r.target ? (_formatWatchTarget(r.target, r.targetType) || '?') : '?') +
254
284
  ' <span class="dispatch-type explore">' + escHtml(r && r.targetType ? r.targetType : '?') + '</span>' +
255
285
  ' <span style="color:var(--blue)">' + escHtml(_conditionLabel(r && r.condition ? r.condition : '?')) + '</span>' +
256
286
  '</div>';
@@ -300,7 +330,7 @@ function openWatchDetail(id) {
300
330
  var condLabel = _conditionLabel(w.condition);
301
331
 
302
332
  // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: watch description/target/id)
303
- document.getElementById('modal-title').innerHTML = escHtml(w.description || w.target) +
333
+ document.getElementById('modal-title').innerHTML = escHtml(_watchTitleFallback(w)) +
304
334
  ' <div style="display:flex;gap:4px;margin-top:4px">' +
305
335
  (w.status === 'active' ? '<button class="pr-pager-btn" style="font-size:var(--text-sm);padding:2px 10px;color:var(--yellow)" onclick="toggleWatchPause(\'' + escHtml(w.id) + '\',true);closeModal()">Pause</button>' : '') +
306
336
  (w.status === 'paused' ? '<button class="pr-pager-btn" style="font-size:var(--text-sm);padding:2px 10px;color:var(--green)" onclick="toggleWatchPause(\'' + escHtml(w.id) + '\',false);closeModal()">Resume</button>' : '') +
@@ -309,7 +339,7 @@ function openWatchDetail(id) {
309
339
 
310
340
  var body = '<div style="display:flex;flex-direction:column;gap:10px;font-size:var(--text-md);line-height:1.6">' +
311
341
  '<div><strong style="color:var(--muted)">ID:</strong> ' + escHtml(w.id) + '</div>' +
312
- '<div><strong style="color:var(--muted)">Target:</strong> ' + escHtml(w.target) + '</div>' +
342
+ '<div><strong style="color:var(--muted)">Target:</strong> ' + escHtml(_formatWatchTarget(w.target, w.targetType)) + '</div>' +
313
343
  '<div><strong style="color:var(--muted)">Target Type:</strong> <span class="dispatch-type explore">' + escHtml(targetLabel) + '</span></div>' +
314
344
  '<div><strong style="color:var(--muted)">Condition:</strong> <span style="color:var(--blue)">' + escHtml(condLabel) + '</span></div>' +
315
345
  '<div><strong style="color:var(--muted)">Check Interval:</strong> ' + escHtml(_intervalToHuman(w.interval)) + '</div>' +
@@ -791,4 +821,7 @@ window.MinionsWatches = {
791
821
  _updateRequireConditionOptions: _updateRequireConditionOptions,
792
822
  // P-w15f1d4b — Phase 7.2: history table helper exposed for testability.
793
823
  _renderWatchHistoryDetail: _renderWatchHistoryDetail,
824
+ // W-mq1j5f9z00030b8f — target / title fallback helpers exposed for unit coverage.
825
+ _formatWatchTarget: _formatWatchTarget,
826
+ _watchTitleFallback: _watchTitleFallback,
794
827
  };
@@ -1080,6 +1080,12 @@ function createStreamConsumer(ctx) {
1080
1080
  let copilotMessageBuffer = '';
1081
1081
  let terminalText = '';
1082
1082
  let taskCompleteSummary = '';
1083
+ // Tracks whether a non-tool `assistant.message` has already pushed terminal
1084
+ // text in this stream. Used to insert a `\n\n` paragraph break between
1085
+ // back-to-back terminating messages so consumers don't render the welded
1086
+ // form `...prev.next...`. First push is NEVER prefixed (would leave every
1087
+ // reply with a leading blank line). Reset by reset() on stream restart.
1088
+ let _hadPriorAssistantMessage = false;
1083
1089
 
1084
1090
  function _captureTaskComplete(summary, success = true, { clearBuffer = false } = {}) {
1085
1091
  if (typeof summary !== 'string' || !summary) return;
@@ -1131,8 +1137,17 @@ function createStreamConsumer(ctx) {
1131
1137
  // the terminal answer. A non-tool assistant.message overrides any
1132
1138
  // streamed deltas (Copilot's authoritative final text for the turn).
1133
1139
  if (content && !hasTools) {
1134
- terminalText = content;
1135
- ctx.pushText(content);
1140
+ // Second-and-later terminating message in a single turn: prefix the
1141
+ // pushed text with `\n\n` so the dashboard merge sees a clean
1142
+ // paragraph boundary instead of welding `<prev>.<next>` together
1143
+ // (W-mq1jwwqo000f20d9 — CC chunk welding fix). The first push must
1144
+ // NOT be prefixed or every reply starts with a blank line. Tool-use
1145
+ // boundaries are skipped — those get rendered as visible chips
1146
+ // client-side and already act as natural separators.
1147
+ const pushed = _hadPriorAssistantMessage ? '\n\n' + content : content;
1148
+ terminalText = _hadPriorAssistantMessage ? terminalText + '\n\n' + content : content;
1149
+ ctx.pushText(pushed);
1150
+ _hadPriorAssistantMessage = true;
1136
1151
  }
1137
1152
  copilotMessageBuffer = '';
1138
1153
  }
@@ -1168,6 +1183,7 @@ function createStreamConsumer(ctx) {
1168
1183
  copilotMessageBuffer = '';
1169
1184
  terminalText = '';
1170
1185
  taskCompleteSummary = '';
1186
+ _hadPriorAssistantMessage = false;
1171
1187
  }
1172
1188
 
1173
1189
  return { consume, reset };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2129",
3
+ "version": "0.1.2131",
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"