@yemi33/minions 0.1.1590 → 0.1.1592

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,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1592 (2026-04-28)
4
+
5
+ ### Features
6
+ - show runtime tag (Claude/Copilot/...) next to agent name
7
+
8
+ ### Other
9
+ - Pretty-print Copilot live output
10
+
3
11
  ## 0.1.1590 (2026-04-28)
4
12
 
5
13
  ### Features
@@ -6,6 +6,11 @@ let _steerInFlight = false;
6
6
  let _lastRenderedText = '';
7
7
  let _runtimeTimer = null;
8
8
 
9
+ function _currentAgentRuntime() {
10
+ var agent = (agentData || []).find(function(a) { return a.id === currentAgentId; });
11
+ return agent && agent.runtime ? agent.runtime : '';
12
+ }
13
+
9
14
  function _updateRuntimeCounter() {
10
15
  var el = document.getElementById('live-runtime');
11
16
  if (!el) return;
@@ -75,8 +80,9 @@ async function refreshLiveOutput() {
75
80
  const el = document.getElementById('live-messages');
76
81
  if (el) {
77
82
  const wasAtBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 150;
83
+ const incrementalSafe = _currentAgentRuntime() !== 'copilot';
78
84
  // Incremental render: only parse new content if text is an extension of previous
79
- if (_lastRenderedText && text.length > _lastRenderedText.length && text.startsWith(_lastRenderedText.slice(0, 200))) {
85
+ if (incrementalSafe && _lastRenderedText && text.length > _lastRenderedText.length && text.startsWith(_lastRenderedText.slice(0, 200))) {
80
86
  renderLiveChatMessage(text.slice(_lastRenderedText.length));
81
87
  } else {
82
88
  el.innerHTML = '';
@@ -1,12 +1,23 @@
1
1
  // dashboard/js/render-agents.js — Agent grid rendering extracted from dashboard.html
2
2
 
3
+ // Per-runtime display labels and accent colors. Add a new entry here when a
4
+ // new runtime is registered in engine/runtimes/index.js.
5
+ const RUNTIME_TAGS = {
6
+ claude: { label: 'Claude', color: 'var(--blue)' },
7
+ copilot: { label: 'Copilot', color: 'var(--purple)' },
8
+ };
9
+ function _runtimeTagHtml(runtime) {
10
+ const meta = RUNTIME_TAGS[runtime] || { label: runtime || 'unknown', color: 'var(--muted)' };
11
+ return '<span class="agent-runtime-tag" title="Runtime: ' + escapeHtml(runtime || 'unknown') + '" style="font-size:9px;font-weight:600;letter-spacing:0.4px;text-transform:uppercase;padding:1px 5px;margin-left:6px;border:1px solid ' + meta.color + ';border-radius:3px;color:' + meta.color + ';background:transparent">' + escapeHtml(meta.label) + '</span>';
12
+ }
13
+
3
14
  function renderAgents(agents) {
4
15
  agentData = agents;
5
16
  const grid = document.getElementById('agents-grid');
6
17
  grid.innerHTML = agents.map(a => `
7
18
  <div class="agent-card ${statusColor(a.status)}" onclick="if(shouldIgnoreSelectionClick(event))return;openAgentDetail('${escapeHtml(a.id)}')">
8
19
  <div class="agent-card-header">
9
- <span class="agent-name"><span class="agent-emoji">${escapeHtml(a.emoji)}</span>${escapeHtml(a.name)}</span>
20
+ <span class="agent-name"><span class="agent-emoji">${escapeHtml(a.emoji)}</span>${escapeHtml(a.name)}${_runtimeTagHtml(a.runtime)}</span>
10
21
  <span class="status-badge ${escapeHtml(a.status)}">${escapeHtml(a.status)}</span>
11
22
  </div>
12
23
  <div class="agent-role">${escapeHtml(a.role)}</div>
@@ -38,9 +49,17 @@ async function openAgentDetail(id) {
38
49
  const emojiSpan = document.createElement('span');
39
50
  emojiSpan.style.fontSize = '22px';
40
51
  emojiSpan.textContent = agent.emoji || '';
52
+ // Runtime tag rendered as a separate span so the textContent path keeps its
53
+ // SEC-03-Phase-A safety for the user-controlled name/role fields.
54
+ const runtimeMeta = RUNTIME_TAGS[agent.runtime] || { label: agent.runtime || 'unknown', color: 'var(--muted)' };
55
+ const runtimeSpan = document.createElement('span');
56
+ runtimeSpan.style.cssText = 'font-size:10px;font-weight:600;letter-spacing:0.4px;text-transform:uppercase;padding:2px 6px;margin-left:10px;border:1px solid ' + runtimeMeta.color + ';border-radius:3px;color:' + runtimeMeta.color;
57
+ runtimeSpan.title = 'Runtime: ' + (agent.runtime || 'unknown');
58
+ runtimeSpan.textContent = runtimeMeta.label;
41
59
  nameEl.replaceChildren(
42
60
  emojiSpan,
43
- document.createTextNode(' ' + (agent.name || '') + ' \u2014 ' + (agent.role || ''))
61
+ document.createTextNode(' ' + (agent.name || '') + ' \u2014 ' + (agent.role || '')),
62
+ runtimeSpan,
44
63
  );
45
64
 
46
65
  const badgeClass = agent.status;
@@ -54,8 +54,39 @@ function formatToolSummary(name, input) {
54
54
  * @param {object} obj - Parsed JSON object from agent JSONL output
55
55
  * @returns {string} HTML fragment
56
56
  */
57
- function _renderJsonObj(obj) {
57
+ function _renderJsonObj(obj, state) {
58
+ state = state || {};
58
59
  var parts = [];
60
+ if (!(state.copilotToolKeys instanceof Set)) state.copilotToolKeys = new Set();
61
+ if (typeof state.copilotDeltaBuffer !== 'string') state.copilotDeltaBuffer = '';
62
+ if (typeof state.copilotReasoningPending !== 'boolean') state.copilotReasoningPending = false;
63
+
64
+ function assistantBubbleHtml(text) {
65
+ return '<div style="display:flex;align-items:baseline;gap:6px;margin:4px 0">' +
66
+ '<span style="color:var(--muted);font-size:10px;flex-shrink:0">&#9679;</span>' +
67
+ '<div style="background:var(--surface2);padding:8px 12px;border-radius:12px 12px 12px 2px;max-width:90%;font-size:12px;word-break:break-word">' + renderMd(text) + '</div>' +
68
+ '</div>';
69
+ }
70
+ function toolUseHtml(name, input) {
71
+ var summary = formatToolSummary(name || 'tool', input || {});
72
+ var rawJson = escHtml(JSON.stringify(input || {}, null, 2).slice(0, 500));
73
+ return '<div style="display:flex;align-items:center;gap:4px;margin:2px 0;font-size:10px;color:var(--muted);font-family:monospace">' +
74
+ '<span style="flex-shrink:0">&#9679;</span>' +
75
+ '<span>' + summary + '</span>' +
76
+ '<span style="cursor:pointer;opacity:0.6;margin-left:4px" onclick="var t=this.parentElement.nextElementSibling;t.style.display=t.style.display===\'none\'?\'block\':\'none\';this.textContent=t.style.display===\'none\'?\'[+]\':\'[-]\'">[+]</span>' +
77
+ '</div>' +
78
+ '<div style="display:none;background:var(--bg);padding:4px 8px;border-radius:4px;margin:0 0 4px 16px;font-size:10px;font-family:monospace;white-space:pre-wrap;max-height:200px;overflow-y:auto;color:var(--muted)">' + rawJson + '</div>';
79
+ }
80
+ function toolResultHtml(text) {
81
+ if (!text || text.length <= 10) return '';
82
+ var truncated = text.length > 3000;
83
+ var displayText = truncated ? text.slice(0, 3000) + '...' : text;
84
+ return '<div style="background:var(--surface);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:160px;overflow-y:auto;white-space:pre-wrap;cursor:pointer" onclick="this.style.maxHeight=this.style.maxHeight===\'160px\'?\'none\':\'160px\'">' + escHtml(displayText) + '</div>';
85
+ }
86
+ function toolKey(name, input) {
87
+ try { return String(name || 'tool') + '|' + JSON.stringify(input || {}); }
88
+ catch { return String(name || 'tool'); }
89
+ }
59
90
 
60
91
  if (obj.type === 'assistant' && obj.message && obj.message.content) {
61
92
  var content = obj.message.content;
@@ -65,22 +96,10 @@ function _renderJsonObj(obj) {
65
96
  parts.push('<div style="font-size:10px;color:var(--muted);padding:2px 8px;font-style:italic">\u{1F4AD} Thinking...</div>');
66
97
  }
67
98
  if (block.type === 'text' && block.text) {
68
- parts.push('<div style="display:flex;align-items:baseline;gap:6px;margin:4px 0">' +
69
- '<span style="color:var(--muted);font-size:10px;flex-shrink:0">&#9679;</span>' +
70
- '<div style="background:var(--surface2);padding:8px 12px;border-radius:12px 12px 12px 2px;max-width:90%;font-size:12px;word-break:break-word">' + renderMd(block.text) + '</div>' +
71
- '</div>');
99
+ parts.push(assistantBubbleHtml(block.text));
72
100
  }
73
101
  if (block.type === 'tool_use') {
74
- var summary = formatToolSummary(block.name || 'tool', block.input || {});
75
- var rawJson = escHtml(JSON.stringify(block.input || {}, null, 2).slice(0, 500));
76
- parts.push(
77
- '<div style="display:flex;align-items:center;gap:4px;margin:2px 0;font-size:10px;color:var(--muted);font-family:monospace">' +
78
- '<span style="flex-shrink:0">&#9679;</span>' +
79
- '<span>' + summary + '</span>' +
80
- '<span style="cursor:pointer;opacity:0.6;margin-left:4px" onclick="var t=this.parentElement.nextElementSibling;t.style.display=t.style.display===\'none\'?\'block\':\'none\';this.textContent=t.style.display===\'none\'?\'[+]\':\'[-]\'">[+]</span>' +
81
- '</div>' +
82
- '<div style="display:none;background:var(--bg);padding:4px 8px;border-radius:4px;margin:0 0 4px 16px;font-size:10px;font-family:monospace;white-space:pre-wrap;max-height:200px;overflow-y:auto;color:var(--muted)">' + rawJson + '</div>'
83
- );
102
+ parts.push(toolUseHtml(block.name, block.input));
84
103
  }
85
104
  }
86
105
  }
@@ -88,11 +107,57 @@ function _renderJsonObj(obj) {
88
107
  if (obj.type === 'tool_result' || (obj.type === 'user' && obj.message && obj.message.content && obj.message.content[0] && obj.message.content[0].type === 'tool_result')) {
89
108
  var tc = (obj.message && obj.message.content && obj.message.content[0] && obj.message.content[0].content) || obj.content || '';
90
109
  var text = typeof tc === 'string' ? tc : JSON.stringify(tc);
91
- if (text.length > 10) {
92
- var truncated = text.length > 3000;
93
- var displayText = truncated ? text.slice(0, 3000) + '...' : text;
94
- parts.push('<div style="background:var(--surface);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:160px;overflow-y:auto;white-space:pre-wrap;cursor:pointer" onclick="this.style.maxHeight=this.style.maxHeight===\'160px\'?\'none\':\'160px\'">' + escHtml(displayText) + '</div>');
110
+ parts.push(toolResultHtml(text));
111
+ }
112
+
113
+ if (obj.type === 'assistant.reasoning' || obj.type === 'assistant.reasoning_delta') {
114
+ state.copilotReasoningPending = true;
115
+ }
116
+
117
+ if (obj.type === 'assistant.message_delta' && typeof obj.data?.deltaContent === 'string') {
118
+ if (state.copilotReasoningPending) {
119
+ parts.push('<div style="font-size:10px;color:var(--muted);padding:2px 8px;font-style:italic">\u{1F4AD} Thinking...</div>');
120
+ state.copilotReasoningPending = false;
95
121
  }
122
+ state.copilotDeltaBuffer += obj.data.deltaContent;
123
+ }
124
+
125
+ if (obj.type === 'assistant.message') {
126
+ if (state.copilotReasoningPending) {
127
+ parts.push('<div style="font-size:10px;color:var(--muted);padding:2px 8px;font-style:italic">\u{1F4AD} Thinking...</div>');
128
+ state.copilotReasoningPending = false;
129
+ }
130
+ state.copilotDeltaBuffer = '';
131
+ if (typeof obj.data?.content === 'string' && obj.data.content) {
132
+ parts.push(assistantBubbleHtml(obj.data.content));
133
+ }
134
+ var toolRequests = Array.isArray(obj.data?.toolRequests) ? obj.data.toolRequests : [];
135
+ for (var trIdx = 0; trIdx < toolRequests.length; trIdx++) {
136
+ var tr = toolRequests[trIdx];
137
+ if (!tr || !tr.name) continue;
138
+ var trInput = tr.arguments || {};
139
+ var trKey = toolKey(tr.name, trInput);
140
+ if (state.copilotToolKeys.has(trKey)) continue;
141
+ state.copilotToolKeys.add(trKey);
142
+ parts.push(toolUseHtml(tr.name, trInput));
143
+ }
144
+ }
145
+
146
+ if (obj.type === 'tool.execution_start' && obj.data?.toolName) {
147
+ var startInput = obj.data.arguments || {};
148
+ var startKey = toolKey(obj.data.toolName, startInput);
149
+ if (!state.copilotToolKeys.has(startKey)) {
150
+ state.copilotToolKeys.add(startKey);
151
+ parts.push(toolUseHtml(obj.data.toolName, startInput));
152
+ }
153
+ }
154
+
155
+ if (obj.type === 'tool.execution_complete') {
156
+ var resultData = obj.data?.result;
157
+ var resultText = resultData?.content || resultData?.detailedContent || '';
158
+ if (!resultText && resultData && typeof resultData !== 'string') resultText = JSON.stringify(resultData);
159
+ if (!resultText && obj.data?.success === false) resultText = 'Tool failed';
160
+ parts.push(toolResultHtml(typeof resultText === 'string' ? resultText : String(resultText || '')));
96
161
  }
97
162
 
98
163
  if (obj.type === 'result') {
@@ -116,6 +181,21 @@ function renderAgentOutput(text) {
116
181
  if (!text) return '';
117
182
  var fragments = [];
118
183
  var lines = text.split('\n');
184
+ var state = { copilotDeltaBuffer: '', copilotToolKeys: new Set(), copilotReasoningPending: false };
185
+
186
+ function flushCopilotPending() {
187
+ if (state.copilotReasoningPending) {
188
+ fragments.push('<div style="font-size:10px;color:var(--muted);padding:2px 8px;font-style:italic">\u{1F4AD} Thinking...</div>');
189
+ state.copilotReasoningPending = false;
190
+ }
191
+ if (state.copilotDeltaBuffer) {
192
+ fragments.push('<div style="display:flex;align-items:baseline;gap:6px;margin:4px 0">' +
193
+ '<span style="color:var(--muted);font-size:10px;flex-shrink:0">&#9679;</span>' +
194
+ '<div style="background:var(--surface2);padding:8px 12px;border-radius:12px 12px 12px 2px;max-width:90%;font-size:12px;word-break:break-word">' + renderMd(state.copilotDeltaBuffer) + '</div>' +
195
+ '</div>');
196
+ state.copilotDeltaBuffer = '';
197
+ }
198
+ }
119
199
 
120
200
  for (var i = 0; i < lines.length; i++) {
121
201
  var trimmed = lines[i].trim();
@@ -145,7 +225,7 @@ function renderAgentOutput(text) {
145
225
  try {
146
226
  var arr = JSON.parse(trimmed);
147
227
  if (Array.isArray(arr)) {
148
- for (var j = 0; j < arr.length; j++) fragments.push(_renderJsonObj(arr[j]));
228
+ for (var j = 0; j < arr.length; j++) fragments.push(_renderJsonObj(arr[j], state));
149
229
  continue;
150
230
  }
151
231
  } catch (e) { /* fall through */ }
@@ -154,11 +234,17 @@ function renderAgentOutput(text) {
154
234
  // JSON object line
155
235
  if (trimmed.startsWith('{')) {
156
236
  try {
157
- fragments.push(_renderJsonObj(JSON.parse(trimmed)));
237
+ var obj = JSON.parse(trimmed);
238
+ if (obj.type !== 'assistant.message_delta' && obj.type !== 'assistant.reasoning' && obj.type !== 'assistant.reasoning_delta' && obj.type !== 'assistant.message') {
239
+ flushCopilotPending();
240
+ }
241
+ fragments.push(_renderJsonObj(obj, state));
158
242
  continue;
159
243
  } catch (e) { /* fall through */ }
160
244
  }
161
245
 
246
+ flushCopilotPending();
247
+
162
248
  // Stderr
163
249
  if (trimmed.startsWith('[stderr]')) {
164
250
  fragments.push('<div style="font-size:9px;color:var(--red);font-family:monospace;padding:1px 4px">' + escHtml(trimmed) + '</div>');
@@ -168,6 +254,7 @@ function renderAgentOutput(text) {
168
254
  }
169
255
  }
170
256
 
257
+ flushCopilotPending();
171
258
  return fragments.join('');
172
259
  }
173
260
 
package/engine/queries.js CHANGED
@@ -399,6 +399,10 @@ function getAgents(config) {
399
399
  const allInboxFiles = safeReadDir(INBOX_DIR);
400
400
 
401
401
  return roster.map(a => {
402
+ // Resolve which CLI runtime this agent dispatches to: per-agent override
403
+ // → engine.defaultCli → 'claude'. Surfaced so the dashboard can show a
404
+ // runtime tag next to the agent name.
405
+ const runtime = shared.resolveAgentCli(a, config.engine || {});
402
406
  const inboxFiles = allInboxFiles.filter(f => f.includes(a.id));
403
407
  const s = getAgentStatus(a.id); // derives from dispatch.json
404
408
 
@@ -414,7 +418,7 @@ function getAgents(config) {
414
418
  const chartered = fs.existsSync(path.join(AGENTS_DIR, a.id, 'charter.md'));
415
419
  if (lastAction.length > 120) lastAction = lastAction.slice(0, 120) + '...';
416
420
  return {
417
- ...a, status: s.status, lastAction,
421
+ ...a, runtime, status: s.status, lastAction,
418
422
  currentTask: (s.task || '').slice(0, 200),
419
423
  resultSummary: (s.resultSummary || '').slice(0, 500),
420
424
  started_at: s.started_at || null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1590",
3
+ "version": "0.1.1592",
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"