@yemi33/minions 0.1.75 → 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,18 @@
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
+
3
16
  ## 0.1.75 (2026-03-31)
4
17
 
5
18
  ### 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>'
@@ -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.75",
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"