@yemi33/minions 0.1.74 → 0.1.76

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,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.76 (2026-03-31)
4
+
5
+ ### Engine
6
+ - engine/lifecycle.js
7
+ - engine/meeting.js
8
+ - engine/shared.js
9
+ - engine/timeout.js
10
+
11
+ ### Dashboard
12
+ - dashboard/js/live-stream.js
13
+ - dashboard/js/render-plans.js
14
+ - dashboard/js/render-prd.js
15
+
16
+ ## 0.1.75 (2026-03-31)
17
+
18
+ ### Dashboard
19
+ - dashboard.js
20
+
3
21
  ## 0.1.74 (2026-03-31)
4
22
 
5
23
  ### Dashboard
@@ -7,10 +7,38 @@ function renderLiveChatMessage(raw) {
7
7
  const el = document.getElementById('live-messages');
8
8
  if (!el) return;
9
9
 
10
+ function renderJsonObj(obj) {
11
+ if (obj.type === 'assistant' && obj.message?.content) {
12
+ for (const block of obj.message.content) {
13
+ if (block.type === 'thinking') {
14
+ el.innerHTML += '<div style="font-size:10px;color:var(--muted);padding:2px 8px;font-style:italic">\u{1F4AD} Thinking...</div>';
15
+ }
16
+ if (block.type === 'text' && block.text) {
17
+ el.innerHTML += '<div style="background:var(--surface2);padding:8px 12px;border-radius:12px 12px 12px 2px;max-width:90%;margin:4px 0;font-size:12px;white-space:pre-wrap;word-break:break-word">' + escHtml(block.text) + '</div>';
18
+ }
19
+ if (block.type === 'tool_use') {
20
+ el.innerHTML += '<div style="background:var(--surface);border:1px solid var(--border);padding:4px 8px;border-radius:4px;margin:2px 0;font-size:10px;color:var(--muted);cursor:pointer" onclick="this.nextElementSibling.style.display=this.nextElementSibling.style.display===\'none\'?\'block\':\'none\'">' +
21
+ '\u{1F527} ' + escHtml(block.name || 'tool') + '</div>' +
22
+ '<div style="display:none;background:var(--bg);padding:4px 8px;border-radius:4px;margin:0 0 4px;font-size:10px;font-family:monospace;white-space:pre-wrap;max-height:200px;overflow-y:auto;color:var(--muted)">' + escHtml(JSON.stringify(block.input, null, 2).slice(0, 500)) + '</div>';
23
+ }
24
+ }
25
+ }
26
+ if (obj.type === 'tool_result' || (obj.type === 'user' && obj.message?.content?.[0]?.type === 'tool_result')) {
27
+ const content = obj.message?.content?.[0]?.content || obj.content || '';
28
+ const text = typeof content === 'string' ? content : JSON.stringify(content);
29
+ if (text.length > 10) {
30
+ el.innerHTML += '<div style="background:var(--bg);border-left:2px solid var(--border);padding:2px 8px;margin:0 0 2px 16px;font-size:9px;font-family:monospace;color:var(--muted);max-height:100px;overflow-y:auto;white-space:pre-wrap;cursor:pointer" onclick="this.style.maxHeight=this.style.maxHeight===\'100px\'?\'none\':\'100px\'">' + escHtml(text.slice(0, 1000)) + (text.length > 1000 ? '...' : '') + '</div>';
31
+ }
32
+ }
33
+ if (obj.type === 'result') {
34
+ el.innerHTML += '<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>';
35
+ }
36
+ }
37
+
10
38
  const lines = raw.split('\n');
11
39
  for (const line of lines) {
12
40
  const trimmed = line.trim();
13
- if (!trimmed) continue;
41
+ if (!trimmed || trimmed.startsWith('#')) continue;
14
42
 
15
43
  // Human steering messages
16
44
  if (trimmed.startsWith('[human-steering]')) {
@@ -24,41 +52,17 @@ function renderLiveChatMessage(raw) {
24
52
  continue;
25
53
  }
26
54
 
27
- // Try to parse as JSON (stream-json format)
28
- if (trimmed.startsWith('{')) {
55
+ // JSON array format (--output-format json)
56
+ if (trimmed.startsWith('[')) {
29
57
  try {
30
- const obj = JSON.parse(trimmed);
31
-
32
- // Assistant text message
33
- if (obj.type === 'assistant' && obj.message?.content) {
34
- for (const block of obj.message.content) {
35
- if (block.type === 'text' && block.text) {
36
- el.innerHTML += '<div style="background:var(--surface2);padding:8px 12px;border-radius:12px 12px 12px 2px;max-width:90%;margin:4px 0;font-size:12px;white-space:pre-wrap;word-break:break-word">' + escHtml(block.text) + '</div>';
37
- }
38
- if (block.type === 'tool_use') {
39
- el.innerHTML += '<div style="background:var(--surface);border:1px solid var(--border);padding:4px 8px;border-radius:4px;margin:2px 0;font-size:10px;color:var(--muted);cursor:pointer" onclick="this.nextElementSibling.style.display=this.nextElementSibling.style.display===\'none\'?\'block\':\'none\'">' +
40
- '🔧 ' + escHtml(block.name || 'tool') + '</div>' +
41
- '<div style="display:none;background:var(--bg);padding:4px 8px;border-radius:4px;margin:0 0 4px;font-size:10px;font-family:monospace;white-space:pre-wrap;max-height:200px;overflow-y:auto;color:var(--muted)">' + escHtml(JSON.stringify(block.input, null, 2).slice(0, 500)) + '</div>';
42
- }
43
- }
44
- }
45
-
46
- // Tool result
47
- if (obj.type === 'tool_result' || (obj.type === 'user' && obj.message?.content?.[0]?.type === 'tool_result')) {
48
- const content = obj.message?.content?.[0]?.content || obj.content || '';
49
- const text = typeof content === 'string' ? content : JSON.stringify(content);
50
- if (text.length > 10) {
51
- el.innerHTML += '<div style="background:var(--bg);border-left:2px solid var(--border);padding:2px 8px;margin:0 0 2px 16px;font-size:9px;font-family:monospace;color:var(--muted);max-height:100px;overflow-y:auto;white-space:pre-wrap;cursor:pointer" onclick="this.style.maxHeight=this.style.maxHeight===\'100px\'?\'none\':\'100px\'">' + escHtml(text.slice(0, 1000)) + (text.length > 1000 ? '...' : '') + '</div>';
52
- }
53
- }
54
-
55
- // Result (final)
56
- if (obj.type === 'result') {
57
- el.innerHTML += '<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)">✓ Task complete</div>';
58
- }
58
+ const arr = JSON.parse(trimmed);
59
+ if (Array.isArray(arr)) { for (const obj of arr) renderJsonObj(obj); continue; }
60
+ } catch { /* fall through to raw text */ }
61
+ }
59
62
 
60
- continue;
61
- } catch { /* JSON parse fallback */ }
63
+ // Single JSON object (--output-format stream-json)
64
+ if (trimmed.startsWith('{')) {
65
+ try { renderJsonObj(JSON.parse(trimmed)); continue; } catch { /* fall through */ }
62
66
  }
63
67
 
64
68
  // Fallback: raw text (stderr, non-JSON lines)
@@ -213,12 +213,15 @@ function renderPlans(plans) {
213
213
  'onclick="event.stopPropagation();planExecute(\'' + escHtml(p.file) + '\',\'' + escHtml(p.project) + '\',this)">Execute</button>' : '';
214
214
  const showPause = effectiveStatus === 'in-progress' && prdFile && !isArchived;
215
215
  const showResume = (effectiveStatus === 'paused' || effectiveStatus === 'awaiting-approval') && prdFile && !isArchived;
216
+ const showVerify = effectiveStatus === 'completed' && prdFile && !isArchived;
216
217
  const pauseBtn = showPause ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--yellow)" ' +
217
218
  'onclick="event.stopPropagation();planPause(\'' + escHtml(prdFile) + '\')">Pause</button>' : '';
218
219
  const resumeBtn = showResume
219
220
  ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--green)" ' +
220
221
  'onclick="event.stopPropagation();planApprove(\'' + escHtml(prdFile) + '\')">' + (effectiveStatus === 'awaiting-approval' ? 'Approve' : 'Resume') + '</button>'
221
222
  : '';
223
+ const verifyBtn = showVerify ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--green)" ' +
224
+ 'onclick="event.stopPropagation();triggerVerify(\'' + escHtml(prdFile) + '\')">Verify</button>' : '';
222
225
  const deleteBtn = !isArchived ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--red)" ' +
223
226
  'onclick="event.stopPropagation();planDelete(\'' + escHtml(p.file) + '\')">Delete</button>' : '';
224
227
 
@@ -235,7 +238,7 @@ function renderPlans(plans) {
235
238
  (p.updatedAt ? '<span title="Last updated: ' + p.updatedAt + '">Updated ' + timeAgo(p.updatedAt) + '</span>' : '') +
236
239
  (p.completedAt ? '<span>' + p.completedAt.slice(0, 10) + '</span>' : '') +
237
240
  (p.generatedBy ? '<span>by ' + escHtml(p.generatedBy) + '</span>' : '') +
238
- executeBtn + pauseBtn + resumeBtn + deleteBtn +
241
+ executeBtn + pauseBtn + resumeBtn + verifyBtn + deleteBtn +
239
242
  '</div>' +
240
243
  '</div>' +
241
244
  '</div>' +
@@ -448,10 +451,12 @@ async function planView(file) {
448
451
  'onclick="planPause(\'' + escHtml(normalizedFile) + '\');closeModal()">Pause</button>' : '';
449
452
  const modalResumeBtn = isPaused ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green)" ' +
450
453
  'onclick="planApprove(\'' + escHtml(normalizedFile) + '\');closeModal()">Resume</button>' : '';
454
+ const modalVerifyBtn = isModalCompleted ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green)" ' +
455
+ 'onclick="triggerVerify(\'' + escHtml(normalizedFile) + '\')">Verify</button>' : '';
451
456
 
452
457
  const lastModLabel = lastMod ? '<div style="font-size:10px;color:var(--muted);font-weight:400;margin-top:2px">Last updated: ' + new Date(lastMod).toLocaleString() + '</div>' : '';
453
458
  const actionBtns = '<div style="display:flex;gap:4px;flex-wrap:wrap;margin-top:4px">' +
454
- (modalCompletedLabel || '') + (modalInProgressLabel || '') + (modalExecuteBtn || '') + (modalPauseBtn || '') + (modalResumeBtn || '') +
459
+ (modalCompletedLabel || '') + (modalInProgressLabel || '') + (modalExecuteBtn || '') + (modalPauseBtn || '') + (modalResumeBtn || '') + (modalVerifyBtn || '') +
455
460
  ' <button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--red)" ' +
456
461
  'onclick="planDelete(\'' + escHtml(normalizedFile) + '\')">Delete</button>' +
457
462
  '</div>';
@@ -32,6 +32,8 @@ function renderPrd(prd, prog) {
32
32
  if (prdFile) {
33
33
  if (effectiveStatus === 'awaiting-approval') {
34
34
  actions = ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:4px" onclick="planApprove(\'' + escHtml(prdFile) + '\')">Approve</button>';
35
+ } else if (effectiveStatus === 'completed') {
36
+ actions = ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:4px" onclick="triggerVerify(\'' + escHtml(prdFile) + '\')">Verify</button>';
35
37
  } else if (effectiveStatus === 'in-progress') {
36
38
  actions = ' <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--yellow);border-color:var(--yellow);margin-left:4px" onclick="planPause(\'' + escHtml(prdFile) + '\')">Pause</button>';
37
39
  } else if (effectiveStatus === 'paused') {
@@ -209,11 +211,14 @@ function renderPrdProgress(prog) {
209
211
  '<span onclick="event.stopPropagation();planView(\'' + escHtml(g.sourcePlan || g.file) + '\')" style="color:var(--blue);cursor:pointer;font-size:10px;padding:2px 8px;background:rgba(56,139,253,0.1);border:1px solid rgba(56,139,253,0.3);border-radius:4px" title="Review latest plan changes">Review plan</span>' +
210
212
  '</div>'
211
213
  : '';
214
+ const isCompleted = t && t.allDone;
212
215
  const pauseResumeBtn = isAwaitingApproval
213
216
  ? '<span onclick="event.stopPropagation();planApprove(\'' + escHtml(g.file) + '\')" style="color:var(--green);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">Approve</span>'
214
217
  : isPaused
215
218
  ? '<span onclick="event.stopPropagation();planApprove(\'' + escHtml(g.file) + '\')" style="color:var(--green);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">Resume</span>'
216
- : '<span onclick="event.stopPropagation();planPause(\'' + escHtml(g.file) + '\')" style="color:var(--yellow);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(210,153,34,0.1);border:1px solid rgba(210,153,34,0.3);border-radius:3px">Pause</span>';
219
+ : isCompleted
220
+ ? '<span onclick="event.stopPropagation();triggerVerify(\'' + escHtml(g.file) + '\')" style="color:var(--green);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:3px">Verify</span>'
221
+ : '<span onclick="event.stopPropagation();planPause(\'' + escHtml(g.file) + '\')" style="color:var(--yellow);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(210,153,34,0.1);border:1px solid rgba(210,153,34,0.3);border-radius:3px">Pause</span>';
217
222
  const deleteBtn = '<span onclick="event.stopPropagation();planDelete(\'' + escHtml(g.file) + '\')" style="color:var(--red);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(248,81,73,0.1);border:1px solid rgba(248,81,73,0.3);border-radius:3px">Delete</span>';
218
223
  const sourcePlanLink = g.sourcePlan
219
224
  ? '<span onclick="event.stopPropagation();planView(\'' + escHtml(g.sourcePlan) + '\')" style="color:var(--blue);cursor:pointer;font-size:9px;padding:1px 6px;background:rgba(56,139,253,0.1);border:1px solid rgba(56,139,253,0.3);border-radius:3px" title="View source plan">&#x1F4C4; Plan</span>'
package/dashboard.js CHANGED
@@ -600,17 +600,19 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
600
600
  async function ccDocCall({ message, document, title, filePath, selection, canEdit, isJson }) {
601
601
  const docContext = `## Document Context\n**${title || 'Document'}**${filePath ? ' (`' + filePath + '`)' : ''}${isJson ? ' (JSON)' : ''}\n${selection ? '\n**Selected text:**\n> ' + selection.slice(0, 1500) + '\n' : ''}\n\`\`\`\n${document.slice(0, 20000)}\n\`\`\`\n${canEdit ? '\nIf editing: respond with your explanation, then `---DOCUMENT---` on its own line, then the COMPLETE updated file.' : '\n(Read-only — answer questions only.)'}`;
602
602
 
603
- // Plans: Sonnet with tools for codebase-aware Q&A and edits
603
+ // Plans + meetings: Sonnet with tools for multi-turn codebase-aware Q&A
604
604
  // Everything else: Haiku, 1 turn, no tools — fast
605
605
  const isPlan = filePath && /^plans\//.test(filePath);
606
+ const isMeeting = filePath && /^meetings\//.test(filePath);
607
+ const isRich = isPlan || isMeeting;
606
608
  const result = await ccCall(message, {
607
609
  store: 'doc', sessionKey: filePath || title,
608
610
  extraContext: docContext, label: 'doc-chat',
609
- timeout: isPlan ? 300000 : 60000,
610
- maxTurns: isPlan ? 10 : 1,
611
- model: isPlan ? 'sonnet' : 'haiku',
612
- allowedTools: isPlan ? 'Read,Glob,Grep' : '',
613
- skipStatePreamble: !isPlan,
611
+ timeout: isRich ? 300000 : 60000,
612
+ maxTurns: isRich ? 10 : 1,
613
+ model: isRich ? 'sonnet' : 'haiku',
614
+ allowedTools: isRich ? 'Read,Glob,Grep' : '',
615
+ skipStatePreamble: !isRich,
614
616
  });
615
617
 
616
618
  if (result.code !== 0 || !result.text) {
@@ -3155,6 +3157,32 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3155
3157
  invalidateStatusCache();
3156
3158
  return jsonReply(res, 200, { ok: true });
3157
3159
  }},
3160
+ { method: 'POST', path: '/api/meetings/archive', desc: 'Archive a meeting', params: 'id', handler: async (req, res) => {
3161
+ const body = await readBody(req);
3162
+ if (!body.id) return jsonReply(res, 400, { error: 'id required' });
3163
+ const { archiveMeeting } = require('./engine/meeting');
3164
+ const meeting = archiveMeeting(body.id);
3165
+ if (!meeting) return jsonReply(res, 404, { error: 'Meeting not found' });
3166
+ invalidateStatusCache();
3167
+ return jsonReply(res, 200, { ok: true });
3168
+ }},
3169
+ { method: 'POST', path: '/api/meetings/unarchive', desc: 'Unarchive a meeting', params: 'id', handler: async (req, res) => {
3170
+ const body = await readBody(req);
3171
+ if (!body.id) return jsonReply(res, 400, { error: 'id required' });
3172
+ const { unarchiveMeeting } = require('./engine/meeting');
3173
+ const meeting = unarchiveMeeting(body.id);
3174
+ if (!meeting) return jsonReply(res, 404, { error: 'Meeting not found or not archived' });
3175
+ invalidateStatusCache();
3176
+ return jsonReply(res, 200, { ok: true });
3177
+ }},
3178
+ { method: 'POST', path: '/api/meetings/delete', desc: 'Delete a meeting', params: 'id', handler: async (req, res) => {
3179
+ const body = await readBody(req);
3180
+ if (!body.id) return jsonReply(res, 400, { error: 'id required' });
3181
+ const { deleteMeeting } = require('./engine/meeting');
3182
+ if (!deleteMeeting(body.id)) return jsonReply(res, 404, { error: 'Meeting not found' });
3183
+ invalidateStatusCache();
3184
+ return jsonReply(res, 200, { ok: true });
3185
+ }},
3158
3186
 
3159
3187
  // Engine
3160
3188
  { method: 'POST', path: '/api/engine/wakeup', desc: 'Trigger immediate engine tick via control.json signal', handler: async (req, res) => {
@@ -72,7 +72,7 @@ function checkPlanCompletion(meta, config) {
72
72
  }
73
73
 
74
74
  const doneItems = planItems.filter(w => w.status === 'done' || w.status === 'in-pr');
75
- const failedItems = [];
75
+ const failedItems = planItems.filter(w => w.status === 'failed');
76
76
 
77
77
  // 1. Mark plan as completed
78
78
  plan.status = 'completed';
package/engine/meeting.js CHANGED
@@ -259,8 +259,33 @@ function endMeeting(meetingId) {
259
259
  return meeting;
260
260
  }
261
261
 
262
+ function archiveMeeting(id) {
263
+ const meeting = getMeeting(id);
264
+ if (!meeting) return null;
265
+ meeting.status = 'archived';
266
+ meeting.archivedAt = new Date().toISOString();
267
+ saveMeeting(meeting);
268
+ return meeting;
269
+ }
270
+
271
+ function unarchiveMeeting(id) {
272
+ const meeting = getMeeting(id);
273
+ if (!meeting || meeting.status !== 'archived') return null;
274
+ meeting.status = 'completed';
275
+ delete meeting.archivedAt;
276
+ saveMeeting(meeting);
277
+ return meeting;
278
+ }
279
+
280
+ function deleteMeeting(id) {
281
+ const filePath = path.join(MEETINGS_DIR, id + '.json');
282
+ if (!fs.existsSync(filePath)) return false;
283
+ fs.unlinkSync(filePath);
284
+ return true;
285
+ }
286
+
262
287
  module.exports = {
263
288
  MEETINGS_DIR, getMeetings, getMeeting, saveMeeting, createMeeting,
264
289
  discoverMeetingWork, collectMeetingFindings,
265
- addMeetingNote, advanceMeetingRound, endMeeting,
290
+ addMeetingNote, advanceMeetingRound, endMeeting, archiveMeeting, unarchiveMeeting, deleteMeeting,
266
291
  };
package/engine/shared.js CHANGED
@@ -177,32 +177,48 @@ function gitEnv() {
177
177
  * Single source of truth — used by llm.js, consolidation.js, and lifecycle.js.
178
178
  */
179
179
  function parseStreamJsonOutput(raw, { maxTextLength = 0 } = {}) {
180
- const lines = raw.split('\n');
181
180
  let text = '';
182
181
  let usage = null;
183
182
  let sessionId = null;
183
+
184
+ function extractResult(obj) {
185
+ if (obj.type !== 'result') return false;
186
+ if (obj.result) text = maxTextLength ? obj.result.slice(0, maxTextLength) : obj.result;
187
+ if (obj.session_id) sessionId = obj.session_id;
188
+ if (obj.total_cost_usd || obj.usage) {
189
+ usage = {
190
+ costUsd: obj.total_cost_usd || 0,
191
+ inputTokens: obj.usage?.input_tokens || 0,
192
+ outputTokens: obj.usage?.output_tokens || 0,
193
+ cacheRead: obj.usage?.cache_read_input_tokens || obj.usage?.cacheReadInputTokens || 0,
194
+ cacheCreation: obj.usage?.cache_creation_input_tokens || obj.usage?.cacheCreationInputTokens || 0,
195
+ durationMs: obj.duration_ms || 0,
196
+ numTurns: obj.num_turns || 0,
197
+ };
198
+ }
199
+ return true;
200
+ }
201
+
202
+ const lines = raw.split('\n');
184
203
  for (let i = lines.length - 1; i >= 0; i--) {
185
204
  const line = lines[i].trim();
186
- if (!line || !line.startsWith('{')) continue;
187
- try {
188
- const obj = JSON.parse(line);
189
- if (obj.type === 'result') {
190
- if (obj.result) text = maxTextLength ? obj.result.slice(0, maxTextLength) : obj.result;
191
- if (obj.session_id) sessionId = obj.session_id;
192
- if (obj.total_cost_usd || obj.usage) {
193
- usage = {
194
- costUsd: obj.total_cost_usd || 0,
195
- inputTokens: obj.usage?.input_tokens || 0,
196
- outputTokens: obj.usage?.output_tokens || 0,
197
- cacheRead: obj.usage?.cache_read_input_tokens || obj.usage?.cacheReadInputTokens || 0,
198
- cacheCreation: obj.usage?.cache_creation_input_tokens || obj.usage?.cacheCreationInputTokens || 0,
199
- durationMs: obj.duration_ms || 0,
200
- numTurns: obj.num_turns || 0,
201
- };
205
+ if (!line) continue;
206
+ // Handle JSON array format (--output-format json)
207
+ if (line.startsWith('[')) {
208
+ try {
209
+ const arr = JSON.parse(line);
210
+ for (let j = arr.length - 1; j >= 0; j--) {
211
+ if (extractResult(arr[j])) break;
202
212
  }
203
- break;
204
- }
205
- } catch {}
213
+ if (text || usage) break;
214
+ } catch {}
215
+ }
216
+ // Handle newline-delimited format (--output-format stream-json)
217
+ if (line.startsWith('{')) {
218
+ try {
219
+ if (extractResult(JSON.parse(line))) break;
220
+ } catch {}
221
+ }
206
222
  }
207
223
  return { text, usage, sessionId };
208
224
  }
package/engine/timeout.js CHANGED
@@ -140,11 +140,8 @@ function checkTimeouts(config) {
140
140
  // Extract output text for the output.log
141
141
  const outputLogPath = path.join(AGENTS_DIR, item.agent, 'output.log');
142
142
  try {
143
- const resultLine = liveLog.split('\n').find(l => l.includes('"type":"result"'));
144
- if (resultLine) {
145
- const result = JSON.parse(resultLine);
146
- safeWrite(outputLogPath, `# Output for dispatch ${item.id}\n# Exit code: ${isSuccess ? 0 : 1}\n# Completed: ${ts()}\n# Detected via output scan\n\n## Result\n${result.result || '(no text)'}\n`);
147
- }
143
+ const { text } = shared.parseStreamJsonOutput(liveLog);
144
+ safeWrite(outputLogPath, `# Output for dispatch ${item.id}\n# Exit code: ${isSuccess ? 0 : 1}\n# Completed: ${ts()}\n# Detected via output scan\n\n## Result\n${text || '(no text)'}\n`);
148
145
  } catch (e) { log('warn', 'parse output result: ' + e.message); }
149
146
 
150
147
  completeDispatch(item.id, isSuccess ? 'success' : 'error', 'Completed (detected from output)');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.74",
3
+ "version": "0.1.76",
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"