@yemi33/minions 0.1.942 → 0.1.943

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,10 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.942 (2026-04-14)
3
+ ## 0.1.943 (2026-04-14)
4
4
 
5
5
  ### Features
6
6
  - make ADO poll frequency configurable and ungate reconcilePrs
7
+ - update render-work-items.js output viewer to use renderAgentOutput
8
+ - update detail-panel.js Output Log tab to use renderAgentOutput
9
+ - Update command-center.js to capture tool inputs and use formatted summaries
7
10
  - certificate-based auth for Teams integration (#1027)
11
+ - Create render-utils.js with shared formatting helpers
8
12
  - add adoPollEnabled/ghPollEnabled engine settings
9
13
  - doc-chat abort kills LLM process + queued messages auto-process
10
14
  - add /api/work-items/reopen endpoint and reopen-work-item CC action (#982)
@@ -19,10 +23,9 @@
19
23
  - fix pending-rebases.json race condition with file locking (#964)
20
24
  - add missing resolveWorkItemPath import in engine.js (#963)
21
25
  - bump plan-to-prd max turns from 20 to 35
22
- - add weekly dead code & deprecated cleanup pipeline
23
- - add red dot notification on CC tab when response completes (#934) (#946)
24
26
 
25
27
  ### Fixes
28
+ - use refs/pull/{id}/merge for build status scoping
26
29
  - use repositoryId+reasonFilter for ADO build query
27
30
  - deduplicate work item creation by title
28
31
  - ADO build query uses repositoryId+pullRequest instead of branchName
@@ -42,9 +45,9 @@
42
45
  - prioritize error_max_turns over permission-blocked in classifyFailure (#1001)
43
46
  - optimistic archive hides both PRD and linked source plan simultaneously
44
47
  - align CC tab unread dot to same position as working dot
45
- - pipeline artifact links use pushModalBack instead of setTimeout race (#993)
46
48
 
47
49
  ### Other
50
+ - refactor: Make renderLiveChatMessage a thin wrapper over renderAgentOutput
48
51
  - test(preflight): add unit tests for findClaudeBinary, runPreflight, printPreflight, checkOrExit (#953)
49
52
  - test(meeting): add unit tests for key functions (#957)
50
53
  - [E2E] Architecture meeting Tier 1+2: 6 correctness bugs + 2 nested lock violations + log buffering (#972)
@@ -148,7 +148,11 @@ function ccSwitchTab(id) {
148
148
  var tools = tab._toolsUsed || [];
149
149
  if (tools.length > 0) {
150
150
  html += '<div style="margin-bottom:6px">';
151
- tools.forEach(function(t) { html += '<div style="color:var(--blue);font-size:11px">\uD83D\uDD27 ' + escHtml(t) + '</div>'; });
151
+ tools.forEach(function(t) {
152
+ var name = typeof t === 'string' ? t : t.name;
153
+ var input = typeof t === 'string' ? {} : (t.input || {});
154
+ html += '<div style="color:var(--muted);font-size:10px;font-family:monospace"><span style="flex-shrink:0">&#9679;</span> ' + formatToolSummary(name, input) + '</div>';
155
+ });
152
156
  html += '</div>';
153
157
  }
154
158
  var text = tab._streamedText || '';
@@ -512,7 +516,9 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
512
516
  if (toolsUsed.length > 0) {
513
517
  html += '<div style="margin-bottom:6px">';
514
518
  toolsUsed.forEach(function(t) {
515
- html += '<div style="color:var(--blue);font-size:11px">\uD83D\uDD27 ' + escHtml(t) + '</div>';
519
+ var name = typeof t === 'string' ? t : t.name;
520
+ var input = typeof t === 'string' ? {} : (t.input || {});
521
+ html += '<div style="color:var(--muted);font-size:10px;font-family:monospace"><span style="flex-shrink:0">&#9679;</span> ' + formatToolSummary(name, input) + '</div>';
516
522
  });
517
523
  html += '</div>';
518
524
  }
@@ -572,7 +578,7 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
572
578
  if (activeTab) activeTab._streamedText = streamedText;
573
579
  updateStreamDiv();
574
580
  } else if (evt.type === 'tool') {
575
- toolsUsed.push(evt.name);
581
+ toolsUsed.push({ name: evt.name, input: evt.input || {} });
576
582
  if (activeTab) activeTab._toolsUsed = toolsUsed.slice();
577
583
  updateStreamDiv();
578
584
  if (msgs.scrollHeight - msgs.scrollTop - msgs.clientHeight < 150) msgs.scrollTop = msgs.scrollHeight;
@@ -141,7 +141,7 @@ function renderDetailContent(detail, tab) {
141
141
  html += '<h4>Task History</h4><div class="section">' + renderMd(detail.history || 'No history yet.') + '</div>';
142
142
  el.innerHTML = html;
143
143
  } else if (tab === 'output') {
144
- el.innerHTML = '<div class="section">' + renderMd(detail.outputLog || 'No output log. The coordinator will save agent output here when tasks complete.') + '</div>';
144
+ el.innerHTML = '<div class="section">' + (detail.outputLog ? renderAgentOutput(detail.outputLog) : 'No output log. The coordinator will save agent output here when tasks complete.') + '</div>';
145
145
  }
146
146
  }
147
147
 
@@ -22,70 +22,8 @@ function _updateRuntimeCounter() {
22
22
  function renderLiveChatMessage(raw) {
23
23
  const el = document.getElementById('live-messages');
24
24
  if (!el) return;
25
- const fragments = [];
26
-
27
- function renderJsonObj(obj) {
28
- if (obj.type === 'assistant' && obj.message?.content) {
29
- for (const block of obj.message.content) {
30
- if (block.type === 'thinking') {
31
- fragments.push('<div style="font-size:10px;color:var(--muted);padding:2px 8px;font-style:italic">\u{1F4AD} Thinking...</div>');
32
- }
33
- if (block.type === 'text' && block.text) {
34
- fragments.push('<div style="background:var(--surface2);padding:8px 12px;border-radius:12px 12px 12px 2px;max-width:90%;margin:4px 0;font-size:12px;word-break:break-word">' + renderMd(block.text) + '</div>');
35
- }
36
- if (block.type === 'tool_use') {
37
- fragments.push('<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\'">' +
38
- '\u{1F527} ' + escHtml(block.name || 'tool') + '</div>' +
39
- '<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>');
40
- }
41
- }
42
- }
43
- if (obj.type === 'tool_result' || (obj.type === 'user' && obj.message?.content?.[0]?.type === 'tool_result')) {
44
- const content = obj.message?.content?.[0]?.content || obj.content || '';
45
- const text = typeof content === 'string' ? content : JSON.stringify(content);
46
- if (text.length > 10) {
47
- fragments.push('<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>');
48
- }
49
- }
50
- if (obj.type === 'result') {
51
- fragments.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>');
52
- }
53
- }
54
-
55
- const lines = raw.split('\n');
56
- for (const line of lines) {
57
- const trimmed = line.trim();
58
- if (!trimmed || trimmed.startsWith('#')) continue;
59
-
60
- if (trimmed.startsWith('[human-steering]')) {
61
- const msg = trimmed.replace('[human-steering] ', '');
62
- fragments.push('<div style="align-self:flex-end;background:var(--blue);color:#fff;padding:6px 12px;border-radius:12px 12px 2px 12px;max-width:80%;margin:4px 0;font-size:12px">' + escHtml(msg) +
63
- '<div style="font-size:9px;opacity:0.7;margin-top:2px">\u2713 Queued</div></div>');
64
- continue;
65
- }
66
- if (trimmed.startsWith('[heartbeat]')) continue;
67
- if (trimmed.startsWith('[steering-failed]')) {
68
- const msg = trimmed.replace('[steering-failed] ', '');
69
- fragments.push('<div style="background:rgba(248,81,73,0.1);border:1px solid var(--red);color:var(--red);padding:6px 12px;border-radius:8px;margin:4px 0;font-size:11px">\u26A0 ' + escHtml(msg) + '</div>');
70
- continue;
71
- }
72
- if (trimmed.startsWith('[')) {
73
- try {
74
- const arr = JSON.parse(trimmed);
75
- if (Array.isArray(arr)) { for (const obj of arr) renderJsonObj(obj); continue; }
76
- } catch { /* fall through */ }
77
- }
78
- if (trimmed.startsWith('{')) {
79
- try { renderJsonObj(JSON.parse(trimmed)); continue; } catch { /* fall through */ }
80
- }
81
- if (trimmed.startsWith('[stderr]')) {
82
- fragments.push('<div style="font-size:9px;color:var(--red);font-family:monospace;padding:1px 4px">' + escHtml(trimmed) + '</div>');
83
- } else {
84
- fragments.push('<div style="font-size:10px;color:var(--muted);font-family:monospace;padding:1px 4px">' + escHtml(trimmed) + '</div>');
85
- }
86
- }
87
-
88
- if (fragments.length > 0) el.innerHTML += fragments.join('');
25
+ const html = renderAgentOutput(raw);
26
+ if (html) el.insertAdjacentHTML('beforeend', html);
89
27
 
90
28
  // Auto-scroll
91
29
  if (el.scrollHeight - el.scrollTop - el.clientHeight < 150) {
@@ -162,8 +100,8 @@ async function sendSteering() {
162
100
  // Immediate feedback — show the message right away
163
101
  const el = document.getElementById('live-messages');
164
102
  if (el) {
165
- el.innerHTML += '<div style="align-self:flex-end;background:var(--blue);color:#fff;padding:6px 12px;border-radius:12px 12px 2px 12px;max-width:80%;margin:4px 0;font-size:12px">' + escHtml(message) +
166
- '<div id="steer-pending" style="font-size:9px;opacity:0.7;margin-top:2px">\u2197 Sending...</div></div>';
103
+ el.insertAdjacentHTML('beforeend', '<div style="align-self:flex-end;background:var(--blue);color:#fff;padding:6px 12px;border-radius:12px 12px 2px 12px;max-width:80%;margin:4px 0;font-size:12px">' + escHtml(message) +
104
+ '<div id="steer-pending" style="font-size:9px;opacity:0.7;margin-top:2px">\u2197 Sending...</div></div>');
167
105
  el.scrollTop = el.scrollHeight;
168
106
  }
169
107
  showToast('cmd-toast', 'Steering message sent to ' + currentAgentId, true);
@@ -0,0 +1,174 @@
1
+ // dashboard/js/render-utils.js — Shared formatting helpers for agent output rendering
2
+ // Depends on: escHtml() and renderMd() from utils.js (loaded before this file)
3
+
4
+ /**
5
+ * Returns a one-line human-readable summary for a Claude tool call.
6
+ * @param {string} name - Tool name (e.g. 'Bash', 'Read', 'Edit')
7
+ * @param {object} input - Tool input object
8
+ * @returns {string} Human-readable summary (HTML-escaped)
9
+ */
10
+ function formatToolSummary(name, input) {
11
+ var inp = input || {};
12
+ switch (name) {
13
+ case 'Bash': {
14
+ var cmd = String(inp.command || '');
15
+ if (cmd.length > 80) cmd = cmd.slice(0, 77) + '...';
16
+ return '$ ' + escHtml(cmd);
17
+ }
18
+ case 'Read':
19
+ return 'Reading ' + escHtml(inp.file_path || '');
20
+ case 'Edit':
21
+ return 'Editing ' + escHtml(inp.file_path || '');
22
+ case 'Write':
23
+ return 'Writing ' + escHtml(inp.file_path || '');
24
+ case 'Grep': {
25
+ var pat = escHtml(inp.pattern || '');
26
+ var gPath = escHtml(inp.path || '.');
27
+ return 'Searching `' + pat + '` in ' + gPath;
28
+ }
29
+ case 'Glob':
30
+ return 'Glob ' + escHtml(inp.pattern || '');
31
+ case 'Agent':
32
+ return 'Spawning agent: ' + escHtml(inp.description || '');
33
+ case 'WebFetch':
34
+ return 'Fetch ' + escHtml(inp.url || '');
35
+ case 'WebSearch':
36
+ return 'Search "' + escHtml(inp.query || '') + '"';
37
+ case 'TodoWrite': {
38
+ var items = Array.isArray(inp.todos) ? inp.todos : [];
39
+ return 'Update todos (' + items.length + ' items)';
40
+ }
41
+ default: {
42
+ var keys = Object.keys(inp);
43
+ if (keys.length === 0) return escHtml(name) + '()';
44
+ var firstKey = keys[0];
45
+ var firstVal = String(inp[firstKey] || '');
46
+ if (firstVal.length > 40) firstVal = firstVal.slice(0, 37) + '...';
47
+ return escHtml(name) + '(' + escHtml(firstKey) + ': ' + escHtml(firstVal) + ')';
48
+ }
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Internal helper: renders a single parsed JSON object into an HTML fragment.
54
+ * @param {object} obj - Parsed JSON object from agent JSONL output
55
+ * @returns {string} HTML fragment
56
+ */
57
+ function _renderJsonObj(obj) {
58
+ var parts = [];
59
+
60
+ if (obj.type === 'assistant' && obj.message && obj.message.content) {
61
+ var content = obj.message.content;
62
+ for (var i = 0; i < content.length; i++) {
63
+ var block = content[i];
64
+ if (block.type === 'thinking') {
65
+ parts.push('<div style="font-size:10px;color:var(--muted);padding:2px 8px;font-style:italic">\u{1F4AD} Thinking...</div>');
66
+ }
67
+ 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>');
72
+ }
73
+ 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
+ );
84
+ }
85
+ }
86
+ }
87
+
88
+ 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
+ var tc = (obj.message && obj.message.content && obj.message.content[0] && obj.message.content[0].content) || obj.content || '';
90
+ 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>');
95
+ }
96
+ }
97
+
98
+ if (obj.type === 'result') {
99
+ 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>');
100
+ }
101
+
102
+ return parts.join('');
103
+ }
104
+
105
+ /**
106
+ * Takes raw JSONL agent output text and returns an HTML string with formatted rendering.
107
+ * Parses JSON objects, tool summaries with ● prefix and [+] toggles for raw JSON,
108
+ * tool results with surface tint and 160px max-height, assistant text with ● prefix
109
+ * and markdown rendering, steering bubbles with ❯ prefix, completion indicator,
110
+ * stderr lines, and heartbeat filtering.
111
+ *
112
+ * @param {string} text - Raw JSONL agent output
113
+ * @returns {string} HTML string
114
+ */
115
+ function renderAgentOutput(text) {
116
+ if (!text) return '';
117
+ var fragments = [];
118
+ var lines = text.split('\n');
119
+
120
+ for (var i = 0; i < lines.length; i++) {
121
+ var trimmed = lines[i].trim();
122
+ if (!trimmed || trimmed.startsWith('#')) continue;
123
+
124
+ // Heartbeat — filter out
125
+ if (trimmed.startsWith('[heartbeat]')) continue;
126
+
127
+ // Human steering
128
+ if (trimmed.startsWith('[human-steering]')) {
129
+ var msg = trimmed.replace('[human-steering] ', '');
130
+ fragments.push('<div style="align-self:flex-end;background:var(--blue);color:#fff;padding:6px 12px;border-radius:12px 12px 2px 12px;max-width:80%;margin:4px 0;font-size:12px">' +
131
+ '<span style="margin-right:4px">❯</span>' + escHtml(msg) +
132
+ '<div style="font-size:9px;opacity:0.7;margin-top:2px">\u2713 Queued</div></div>');
133
+ continue;
134
+ }
135
+
136
+ // Steering failed
137
+ if (trimmed.startsWith('[steering-failed]')) {
138
+ var failMsg = trimmed.replace('[steering-failed] ', '');
139
+ fragments.push('<div style="background:rgba(248,81,73,0.1);border:1px solid var(--red);color:var(--red);padding:6px 12px;border-radius:8px;margin:4px 0;font-size:11px">\u26A0 ' + escHtml(failMsg) + '</div>');
140
+ continue;
141
+ }
142
+
143
+ // JSON array line
144
+ if (trimmed.startsWith('[')) {
145
+ try {
146
+ var arr = JSON.parse(trimmed);
147
+ if (Array.isArray(arr)) {
148
+ for (var j = 0; j < arr.length; j++) fragments.push(_renderJsonObj(arr[j]));
149
+ continue;
150
+ }
151
+ } catch (e) { /* fall through */ }
152
+ }
153
+
154
+ // JSON object line
155
+ if (trimmed.startsWith('{')) {
156
+ try {
157
+ fragments.push(_renderJsonObj(JSON.parse(trimmed)));
158
+ continue;
159
+ } catch (e) { /* fall through */ }
160
+ }
161
+
162
+ // Stderr
163
+ if (trimmed.startsWith('[stderr]')) {
164
+ fragments.push('<div style="font-size:9px;color:var(--red);font-family:monospace;padding:1px 4px">' + escHtml(trimmed) + '</div>');
165
+ } else {
166
+ // Plain text fallback
167
+ fragments.push('<div style="font-size:10px;color:var(--muted);font-family:monospace;padding:1px 4px">' + escHtml(trimmed) + '</div>');
168
+ }
169
+ }
170
+
171
+ return fragments.join('');
172
+ }
173
+
174
+ window.MinionsRenderUtils = { formatToolSummary, renderAgentOutput };
@@ -509,12 +509,12 @@ function viewAgentOutput(logPath) {
509
509
  document.getElementById('modal-title').textContent = 'Agent Output';
510
510
  document.getElementById('modal-body').innerHTML = '<p style="color:var(--muted)">Loading...</p>';
511
511
  document.getElementById('modal-body').style.fontFamily = 'Consolas, monospace';
512
- document.getElementById('modal-body').style.whiteSpace = 'pre-wrap';
512
+ document.getElementById('modal-body').style.whiteSpace = 'normal';
513
513
  document.getElementById('modal').classList.add('open');
514
514
  fetch('/api/agent-output?file=' + encodeURIComponent(logPath))
515
515
  .then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
516
516
  .then(function(content) {
517
- document.getElementById('modal-body').textContent = content;
517
+ document.getElementById('modal-body').innerHTML = renderAgentOutput(content);
518
518
  })
519
519
  .catch(function() {
520
520
  document.getElementById('modal-body').innerHTML = '<p style="color:var(--red)">Failed to load output.</p>';
package/dashboard.js CHANGED
@@ -85,7 +85,7 @@ function buildDashboardHtml() {
85
85
 
86
86
  // Assemble JS modules (order matters: utils → state → renderers → commands → refresh)
87
87
  const jsFiles = [
88
- 'utils', 'state', 'detail-panel', 'live-stream',
88
+ 'utils', 'state', 'render-utils', 'detail-panel', 'live-stream',
89
89
  'render-agents', 'render-dispatch', 'render-work-items', 'render-prd',
90
90
  'render-prs', 'render-plans', 'render-inbox', 'render-kb', 'render-skills',
91
91
  'render-other', 'render-schedules', 'render-pipelines', 'render-meetings', 'render-pinned',
@@ -3448,6 +3448,18 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3448
3448
  } catch (e) { ccInFlightTabs.delete(tabId); return jsonReply(res, 500, { error: e.message }); }
3449
3449
  }
3450
3450
 
3451
+ /** Build a lightweight input object for SSE tool events — keeps only the fields formatToolSummary needs, with truncated string values. */
3452
+ function _lightToolInput(input) {
3453
+ if (!input || typeof input !== 'object') return {};
3454
+ const out = {};
3455
+ for (const [k, v] of Object.entries(input)) {
3456
+ if (Array.isArray(v)) { out[k] = v; }
3457
+ else if (typeof v === 'string') { out[k] = v.length > 200 ? v.slice(0, 197) + '...' : v; }
3458
+ else { out[k] = v; }
3459
+ }
3460
+ return out;
3461
+ }
3462
+
3451
3463
  async function handleCommandCenterStream(req, res) {
3452
3464
  if (checkRateLimit('command-center', 10)) { res.statusCode = 429; res.end('Rate limited'); return; }
3453
3465
  try {
@@ -3503,7 +3515,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3503
3515
  try { res.write('data: ' + JSON.stringify({ type: 'chunk', text: display }) + '\n\n'); } catch {}
3504
3516
  },
3505
3517
  onToolUse: (name, input) => {
3506
- try { res.write('data: ' + JSON.stringify({ type: 'tool', name, input: typeof input === 'string' ? input.slice(0, 200) : JSON.stringify(input).slice(0, 200) }) + '\n\n'); } catch {}
3518
+ try { res.write('data: ' + JSON.stringify({ type: 'tool', name, input: _lightToolInput(input) }) + '\n\n'); } catch {}
3507
3519
  }
3508
3520
  });
3509
3521
  _ccStreamAbort = llmPromise.abort;
@@ -3526,7 +3538,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3526
3538
  try { res.write('data: ' + JSON.stringify({ type: 'chunk', text: display }) + '\n\n'); } catch {}
3527
3539
  },
3528
3540
  onToolUse: (name, input) => {
3529
- try { res.write('data: ' + JSON.stringify({ type: 'tool', name, input: typeof input === 'string' ? input.slice(0, 200) : JSON.stringify(input).slice(0, 200) }) + '\n\n'); } catch {}
3541
+ try { res.write('data: ' + JSON.stringify({ type: 'tool', name, input: _lightToolInput(input) }) + '\n\n'); } catch {}
3530
3542
  }
3531
3543
  });
3532
3544
  _ccStreamAbort = retryPromise.abort;
package/engine/ado.js CHANGED
@@ -372,19 +372,19 @@ async function pollPrStatus(config) {
372
372
  if (newStatus !== PR_STATUS.ACTIVE) return updated;
373
373
 
374
374
  // Query builds API directly — status checks (/statuses) are unreliable (stale codecoverage postbacks)
375
- // Use the merge commit hash to find builds for this PR
376
- const mergeCommitId = prData.lastMergeCommit?.commitId;
375
+ // Use refs/pull/{id}/merge — the synthetic merge ref ADO creates for each PR — to scope builds exactly
376
+ const prNumber = pr.prNumber;
377
377
  let buildStatus = 'none';
378
378
  let buildFailReason = '';
379
379
  let buildStatuses = []; // for error log fetching
380
380
 
381
- if (mergeCommitId) {
381
+ if (prNumber) {
382
382
  try {
383
- // Find builds for this PR scope by repositoryId+pullRequest reason to avoid branch-scan window issues
384
- const buildsUrl = `${orgBase}/${project.adoProject}/_apis/build/builds?reasonFilter=pullRequest&repositoryId=${project.repositoryId}&repositoryType=TfsGit&$top=25&api-version=7.1`;
383
+ // branchName=refs/pull/{id}/merge targets exactly this PR's merge-commit builds (server-side filter, no client matching needed)
384
+ const mergeRef = encodeURIComponent(`refs/pull/${prNumber}/merge`);
385
+ const buildsUrl = `${orgBase}/${project.adoProject}/_apis/build/builds?branchName=${mergeRef}&repositoryId=${project.repositoryId}&repositoryType=TfsGit&$top=10&api-version=7.1`;
385
386
  const buildsData = await adoFetch(buildsUrl, token);
386
- // Match by exact merge commit — only current builds, not stale
387
- const prBuilds = (buildsData?.value || []).filter(b => b.sourceVersion === mergeCommitId);
387
+ const prBuilds = buildsData?.value || [];
388
388
 
389
389
  if (prBuilds.length > 0) {
390
390
  // partiallySucceeded = warnings, not failures — counts as passing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.942",
3
+ "version": "0.1.943",
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"