@yemi33/minions 0.1.1605 → 0.1.1607

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 CHANGED
@@ -1,12 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.1605 (2026-04-28)
3
+ ## 0.1.1607 (2026-04-28)
4
4
 
5
5
  ### Features
6
6
  - match runtime tags to actual logos (pixel-crab Claude, mascot Copilot)
7
7
  - replace runtime text tag with inline SVG logos
8
8
 
9
9
  ### Fixes
10
+ - gate live completion banner on final process exit
11
+ - wrap CC Model input so its label/hint survive runtime hydration
10
12
  - preserve reconnect stream state
11
13
  - preserve copilot cc action blocks
12
14
  - improve copilot command center streaming
@@ -52,6 +52,12 @@ function formatToolSummary(name, input) {
52
52
  /**
53
53
  * Internal helper: renders a single parsed JSON object into an HTML fragment.
54
54
  * @param {object} obj - Parsed JSON object from agent JSONL output
55
+ * @param {object} [state] - Mutable render state shared across calls in a single
56
+ * renderAgentOutput pass. Persistent fields: copilotToolKeys (Set), copilotDeltaBuffer,
57
+ * copilotReasoningPending. Per-line fields written by the caller before each call:
58
+ * isFinalResult (true only for the LAST result line before [process-exit]; gates the
59
+ * completion banner so ScheduleWakeup resume cycles don't repeatedly fire it),
60
+ * exitInfo ({ code, success } parsed from [process-exit], or null).
55
61
  * @returns {string} HTML fragment
56
62
  */
57
63
  function _renderJsonObj(obj, state) {
@@ -161,7 +167,19 @@ function _renderJsonObj(obj, state) {
161
167
  }
162
168
 
163
169
  if (obj.type === 'result') {
164
- parts.push('<div style="background:rgba(63,185,80,0.1);border:1px solid var(--green);padding:8px 12px;border-radius:8px;margin:8px 0;font-size:12px;color:var(--green)">\u2713 Task complete</div>');
170
+ // Banner is gated on TWO conditions: (a) this is the LAST result line in the output,
171
+ // and (b) the [process-exit] sentinel has been seen. Intermediate result lines from
172
+ // ScheduleWakeup resume cycles fall through and render nothing.
173
+ if (state.isFinalResult) {
174
+ var subtype = typeof obj.subtype === 'string' ? obj.subtype : '';
175
+ var resultIsError = obj.is_error === true || subtype.startsWith('error');
176
+ var exitFailed = state.exitInfo && state.exitInfo.success === false;
177
+ if (resultIsError || exitFailed) {
178
+ parts.push('<div style="background:rgba(248,81,73,0.1);border:1px solid var(--red);padding:8px 12px;border-radius:8px;margin:8px 0;font-size:12px;color:var(--red)">\u2717 Task ended with error</div>');
179
+ } else {
180
+ parts.push('<div style="background:rgba(63,185,80,0.1);border:1px solid var(--green);padding:8px 12px;border-radius:8px;margin:8px 0;font-size:12px;color:var(--green)">\u2713 Task complete</div>');
181
+ }
182
+ }
165
183
  }
166
184
 
167
185
  return parts.join('');
@@ -197,6 +215,47 @@ function renderAgentOutput(text) {
197
215
  }
198
216
  }
199
217
 
218
+ // ── Pre-scan ──────────────────────────────────────────────────────────────
219
+ // ScheduleWakeup-based polling agents emit one `"type":"result"` JSONL line
220
+ // per resume cycle, followed by another resume — only the LAST result line
221
+ // before spawn-agent.js writes its `[process-exit]` sentinel is the true
222
+ // final result. Intermediate result lines must NOT trigger the banner.
223
+ //
224
+ // spawn-agent.js writes:
225
+ // "\n[process-exit] code=N\n" on normal close (engine/spawn-agent.js:202)
226
+ // "\n[process-exit] spawn-failed\n" on synchronous spawn() throw
227
+ //
228
+ // We pre-scan to find: (1) whether [process-exit] was emitted at all, (2) its
229
+ // exit code (success vs failure), and (3) the line index of the last result
230
+ // strictly before that sentinel.
231
+ var exitInfo = null; // null = process still running (no banner ever fires)
232
+ var exitLineIdx = -1;
233
+ var lastResultLineIdx = -1;
234
+ var exitRe = /^\[process-exit\]\s+(?:code=)?(-?\d+|spawn-failed)\s*$/;
235
+ for (var k = 0; k < lines.length; k++) {
236
+ var t = lines[k].trim();
237
+ if (!t) continue;
238
+ var em = exitRe.exec(t);
239
+ if (em) {
240
+ var token = em[1];
241
+ var code = token === 'spawn-failed' ? -1 : parseInt(token, 10);
242
+ exitInfo = { code: code, success: code === 0 };
243
+ exitLineIdx = k;
244
+ }
245
+ }
246
+
247
+ if (exitLineIdx !== -1) {
248
+ for (var r = 0; r < exitLineIdx; r++) {
249
+ var rt = lines[r].trim();
250
+ if (!rt || rt.charCodeAt(0) !== 123 /* '{' */) continue;
251
+ try {
252
+ var probe = JSON.parse(rt);
253
+ if (probe && probe.type === 'result') lastResultLineIdx = r;
254
+ } catch (e) { /* ignore parse errors during scan */ }
255
+ }
256
+ }
257
+ state.exitInfo = exitInfo;
258
+
200
259
  for (var i = 0; i < lines.length; i++) {
201
260
  var trimmed = lines[i].trim();
202
261
  if (!trimmed || trimmed.startsWith('#')) continue;
@@ -204,6 +263,9 @@ function renderAgentOutput(text) {
204
263
  // Heartbeat — filter out
205
264
  if (trimmed.startsWith('[heartbeat]')) continue;
206
265
 
266
+ // Process-exit sentinel — internal signal, never displayed
267
+ if (trimmed.startsWith('[process-exit]')) continue;
268
+
207
269
  // Human steering
208
270
  if (trimmed.startsWith('[human-steering]')) {
209
271
  var msg = trimmed.replace('[human-steering] ', '');
@@ -220,24 +282,26 @@ function renderAgentOutput(text) {
220
282
  continue;
221
283
  }
222
284
 
223
- // JSON array line
285
+ // JSON array line — never holds the canonical result, banner never fires here
224
286
  if (trimmed.startsWith('[')) {
225
287
  try {
226
288
  var arr = JSON.parse(trimmed);
227
289
  if (Array.isArray(arr)) {
290
+ state.isFinalResult = false;
228
291
  for (var j = 0; j < arr.length; j++) fragments.push(_renderJsonObj(arr[j], state));
229
292
  continue;
230
293
  }
231
294
  } catch (e) { /* fall through */ }
232
295
  }
233
296
 
234
- // JSON object line
297
+ // JSON object line — banner fires only when this is the final result AND process exited
235
298
  if (trimmed.startsWith('{')) {
236
299
  try {
237
300
  var obj = JSON.parse(trimmed);
238
301
  if (obj.type !== 'assistant.message_delta' && obj.type !== 'assistant.reasoning' && obj.type !== 'assistant.reasoning_delta' && obj.type !== 'assistant.message') {
239
302
  flushCopilotPending();
240
303
  }
304
+ state.isFinalResult = (i === lastResultLineIdx) && (exitInfo !== null);
241
305
  fragments.push(_renderJsonObj(obj, state));
242
306
  continue;
243
307
  } catch (e) { /* fall through */ }
@@ -255,6 +319,14 @@ function renderAgentOutput(text) {
255
319
  }
256
320
 
257
321
  flushCopilotPending();
322
+
323
+ // Fallback error banner: process exited with non-zero code but never emitted
324
+ // a `"type":"result"` line (CLI crashed before producing one). Without this,
325
+ // the user would see only stderr noise with no terminal-state indicator.
326
+ if (exitInfo && !exitInfo.success && lastResultLineIdx === -1) {
327
+ fragments.push('<div style="background:rgba(248,81,73,0.1);border:1px solid var(--red);padding:8px 12px;border-radius:8px;margin:8px 0;font-size:12px;color:var(--red)">✗ Task ended with error</div>');
328
+ }
329
+
258
330
  return fragments.join('');
259
331
  }
260
332
 
@@ -179,7 +179,12 @@ async function openSettings() {
179
179
  '</div>' +
180
180
  '<div>' +
181
181
  '<label style="font-size:10px;color:var(--muted);display:block;margin-bottom:2px">CC Model</label>' +
182
- '<input id="set-ccModel" value="' + escHtml(e.ccModel || '') + '" placeholder="(inherits Default Model)" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px">' +
182
+ // Wrap the input in a dedicated container so loadModelsForRuntime
183
+ // (which does `wrap.innerHTML = …` on the input's parent) only
184
+ // swaps THIS element. Without the wrap it would wipe the label
185
+ // and the hint below, breaking vertical alignment with the
186
+ // sibling CC CLI / Effort columns.
187
+ '<div id="set-ccModel-wrap"><input id="set-ccModel" value="' + escHtml(e.ccModel || '') + '" placeholder="(inherits Default Model)" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px"></div>' +
183
188
  '<div style="font-size:9px;color:var(--muted);margin-top:1px">Empty = inherit Default Model</div>' +
184
189
  '</div>' +
185
190
  '<div>' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1605",
3
+ "version": "0.1.1607",
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"