@yemi33/minions 0.1.592 → 0.1.594

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,8 +1,12 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.592 (2026-04-08)
3
+ ## 0.1.594 (2026-04-08)
4
+
5
+ ### Features
6
+ - show worktree count and engine quick stats on engine page
4
7
 
5
8
  ### Fixes
9
+ - Pipeline UI show monitored resources in pipeline card (closes #523) (#542)
6
10
  - move 'When to Stop' after Build/Test in implement playbook
7
11
  - per-type maxTurns bypassed when config has default maxTurns=100
8
12
 
@@ -64,7 +64,16 @@ function _processStatusUpdate(data) {
64
64
  if (_changed('prd', [data.prd, data.prdProgress])) renderPrd(data.prd, data.prdProgress);
65
65
  if (_changed('prs', data.pullRequests)) renderPrs(data.pullRequests || []);
66
66
  if (_changed('archivedPrds', data.archivedPrds)) renderArchiveButtons(data.archivedPrds || []);
67
- if (_changed('engine', data.engine)) renderEngineStatus(data.engine);
67
+ if (_changed('engine', data.engine)) {
68
+ renderEngineStatus(data.engine);
69
+ var qs = document.getElementById('engine-quick-stats');
70
+ if (qs && data.engine) {
71
+ var wt = data.engine.worktreeCount != null ? data.engine.worktreeCount : '-';
72
+ var tick = data.engine.tick || '-';
73
+ var pid = data.engine.pid || '-';
74
+ qs.innerHTML = '<span>PID: <b>' + pid + '</b></span><span>Tick: <b>' + tick + '</b></span><span>Worktrees: <b>' + wt + '</b></span>';
75
+ }
76
+ }
68
77
  if (_changed('version', data.version)) renderVersionBanner(data.version);
69
78
  if (_changed('dispatch', data.dispatch)) renderDispatch(data.dispatch);
70
79
  window._lastDispatch = data.dispatch;
@@ -5,6 +5,62 @@ let _pipelinePollId = null;
5
5
  let _pipelinePollInterval = null;
6
6
  function _stopPipelinePoll() { if (_pipelinePollInterval) { clearInterval(_pipelinePollInterval); _pipelinePollInterval = null; } _pipelinePollId = null; }
7
7
 
8
+ /**
9
+ * Collect all monitoredResources from a pipeline (pipeline-level + all stages).
10
+ * Returns a deduplicated array of resource objects.
11
+ */
12
+ function _collectMonitoredResources(pipeline) {
13
+ var seen = new Set();
14
+ var result = [];
15
+ function add(r) {
16
+ var key = typeof r === 'string' ? r : (r.url || r.label || JSON.stringify(r));
17
+ if (seen.has(key)) return;
18
+ seen.add(key);
19
+ result.push(typeof r === 'string' ? { label: r, url: r } : r);
20
+ }
21
+ (pipeline.monitoredResources || []).forEach(add);
22
+ (pipeline.stages || []).forEach(function(s) { (s.monitoredResources || []).forEach(add); });
23
+ return result;
24
+ }
25
+
26
+ /**
27
+ * Render monitored resources as compact pills on a pipeline card or stage detail.
28
+ * Supports both string resources (URLs/IDs) and objects with {type, label, url}.
29
+ * @param {Array} resources - array of resource strings or {type?, label, url?} objects
30
+ * @param {Object} [options] - { compact: true } limits display and shows "+N more"
31
+ * @returns {string} HTML string
32
+ */
33
+ function _renderMonitoredResources(resources, options) {
34
+ if (!resources || resources.length === 0) return '';
35
+ var compact = options && options.compact;
36
+ var maxShow = compact ? 4 : resources.length;
37
+ var shown = resources.slice(0, maxShow);
38
+ var overflow = resources.length - maxShow;
39
+
40
+ var iconMap = { pr: '🔀', workitem: '⚙', url: '🔗', issue: '🐛' };
41
+ var pillStyle = 'display:inline-flex;align-items:center;gap:2px;padding:1px 6px;border-radius:10px;font-size:10px;text-decoration:none;' +
42
+ 'color:var(--text);background:color-mix(in srgb, var(--muted) 10%, transparent);border:1px solid color-mix(in srgb, var(--muted) 20%, transparent);max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
43
+
44
+ var pills = shown.map(function(r) {
45
+ var res = typeof r === 'string' ? { label: r, url: r.startsWith('http') ? r : '' } : r;
46
+ var icon = iconMap[res.type] || (res.url ? '🔗' : '📌');
47
+ var label = res.label || res.url || '(resource)';
48
+ // Truncate label for compact view
49
+ var displayLabel = compact && label.length > 28 ? label.slice(0, 26) + '…' : label;
50
+ if (res.url) {
51
+ return '<a href="' + escHtml(res.url) + '" target="_blank" rel="noopener" style="' + pillStyle + ';cursor:pointer" onclick="event.stopPropagation()" title="' + escHtml(label) + '">' + icon + ' ' + escHtml(displayLabel) + '</a>';
52
+ }
53
+ return '<span style="' + pillStyle + ';cursor:default" title="' + escHtml(label) + '">' + icon + ' ' + escHtml(displayLabel) + '</span>';
54
+ });
55
+
56
+ if (overflow > 0) {
57
+ pills.push('<span style="' + pillStyle + ';cursor:default;opacity:0.7" title="' + overflow + ' more resource' + (overflow !== 1 ? 's' : '') + '">+' + overflow + ' more</span>');
58
+ }
59
+
60
+ var heading = compact ? '' : '<span style="font-size:10px;color:var(--muted);margin-right:4px">Monitoring:</span>';
61
+ return '<div style="margin-top:4px;display:flex;flex-wrap:wrap;gap:3px;align-items:center">' + heading + pills.join('') + '</div>';
62
+ }
63
+
8
64
  /**
9
65
  * Render clickable artifact links for a pipeline stage.
10
66
  * Each artifact type gets an icon and navigates to the relevant detail view.
@@ -154,6 +210,10 @@ function renderPipelines(pipelines) {
154
210
  progressHtml = _buildProgressBar(p.stages || [], displayRun);
155
211
  }
156
212
 
213
+ // Monitored resources (pipeline-level + stage-level, compact on card)
214
+ var allResources = _collectMonitoredResources(p);
215
+ var resourcesHtml = _renderMonitoredResources(allResources, { compact: true });
216
+
157
217
  return '<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:12px 16px;margin-bottom:8px;cursor:pointer" onclick="openPipelineDetail(\'' + escHtml(p.id) + '\')">' +
158
218
  '<div style="display:flex;justify-content:space-between;align-items:center">' +
159
219
  '<strong style="font-size:13px">' + escHtml(p.title) + '</strong>' +
@@ -164,6 +224,7 @@ function renderPipelines(pipelines) {
164
224
  '</div>' +
165
225
  '</div>' +
166
226
  '<div style="margin-top:6px;display:flex;gap:4px;align-items:center;flex-wrap:wrap">' + stageFlow + '</div>' +
227
+ resourcesHtml +
167
228
  progressHtml +
168
229
  '</div>';
169
230
  }).join('');
@@ -196,6 +257,15 @@ function openPipelineDetail(id) {
196
257
  if (detailRun && (p.stages || []).length > 0) {
197
258
  html += _buildProgressBar(p.stages || [], detailRun, { height: '8px', detailLabel: true });
198
259
  }
260
+ // Pipeline-level monitored resources (full view in detail)
261
+ var pipelineResources = _collectMonitoredResources(p);
262
+ if (pipelineResources.length > 0) {
263
+ html += '<div style="border:1px solid color-mix(in srgb, var(--blue) 20%, transparent);border-radius:6px;padding:6px 10px;background:color-mix(in srgb, var(--blue) 4%, transparent)">' +
264
+ '<span style="font-size:10px;font-weight:600;color:var(--blue)">📡 Monitored Resources</span>' +
265
+ _renderMonitoredResources(pipelineResources) +
266
+ '</div>';
267
+ }
268
+
199
269
  html += '<h4 style="font-size:12px;color:var(--blue);margin:0">Stages</h4>';
200
270
  (p.stages || []).forEach(function(s, i) {
201
271
  var stageRun = activeRun?.stages?.[s.id] || {};
@@ -209,6 +279,7 @@ function openPipelineDetail(id) {
209
279
  '<span style="color:' + statusColor + ';font-size:10px;font-weight:600">' + stageStatus.toUpperCase() + '</span>' +
210
280
  '</div>' +
211
281
  '<div style="font-size:10px;color:var(--muted);margin-top:4px">Type: ' + escHtml(s.type) + ' | Depends on: ' + escHtml(deps) + (s.agent ? ' | Agent: ' + escHtml(s.agent) : '') + '</div>' +
282
+ _renderMonitoredResources(s.monitoredResources || []) +
212
283
  _renderArtifactLinks(stageRun.artifacts) +
213
284
  (stageRun.output ? '<div style="margin-top:6px;font-size:11px;max-height:150px;overflow-y:auto">' + renderMd(stageRun.output.slice(0, 500)) + '</div>' : '') +
214
285
  (stageStatus === 'waiting-human' ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--green);border-color:var(--green);margin-top:6px" onclick="_continuePipeline(\'' + escHtml(id) + '\',\'' + escHtml(s.id) + '\',this)">Continue</button>' : '') +
@@ -1,3 +1,6 @@
1
+ <section>
2
+ <div id="engine-quick-stats" style="display:flex;gap:16px;margin-bottom:12px;font-size:11px;color:var(--muted)"></div>
3
+ </section>
1
4
  <section>
2
5
  <h2>Engine Log <span style="font-size:10px;color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">tick-by-tick audit trail of engine operations</span></h2>
3
6
  <div class="log-list" id="engine-log">No log entries yet.</div>
package/dashboard.js CHANGED
@@ -167,6 +167,21 @@ function getVerifyGuides() {
167
167
  function getArchivedPrds() { return []; }
168
168
  function getEngineState() { return queries.getControl(); }
169
169
 
170
+ function _countWorktrees() {
171
+ try {
172
+ const config = queries.getConfig();
173
+ const projects = queries.getProjects(config);
174
+ let count = 0;
175
+ for (const p of projects) {
176
+ const root = p.localPath ? path.resolve(p.localPath) : null;
177
+ if (!root) continue;
178
+ const wtRoot = path.resolve(root, config.engine?.worktreeRoot || shared.ENGINE_DEFAULTS.worktreeRoot);
179
+ try { count += fs.readdirSync(wtRoot).filter(f => fs.statSync(path.join(wtRoot, f)).isDirectory()).length; } catch {}
180
+ }
181
+ return count;
182
+ } catch { return 0; }
183
+ }
184
+
170
185
  // ── npm update check ────────────────────────────────────────────────────────
171
186
  let _npmVersionCache = null;
172
187
  let _npmVersionCacheTs = 0;
@@ -347,7 +362,7 @@ function getStatus() {
347
362
  pullRequests: getPullRequests(),
348
363
  verifyGuides: getVerifyGuides(),
349
364
  archivedPrds: getArchivedPrds(),
350
- engine: getEngineState(),
365
+ engine: { ...getEngineState(), worktreeCount: _countWorktrees() },
351
366
  dispatch: getDispatchQueue(),
352
367
  engineLog: getEngineLog(),
353
368
  metrics: getMetrics(),
@@ -3937,17 +3952,18 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3937
3952
  const result = pipelines.map(p => ({ ...p, runs: (runs[p.id] || []).slice(-5) }));
3938
3953
  return jsonReply(res, 200, result);
3939
3954
  }},
3940
- { method: 'POST', path: '/api/pipelines', desc: 'Create a pipeline', params: 'id, title, stages[], trigger?', handler: async (req, res) => {
3955
+ { method: 'POST', path: '/api/pipelines', desc: 'Create a pipeline', params: 'id, title, stages[], trigger?, monitoredResources?', handler: async (req, res) => {
3941
3956
  const body = await readBody(req);
3942
3957
  if (!body.id || !body.title || !body.stages) return jsonReply(res, 400, { error: 'id, title, and stages required' });
3943
3958
  const { savePipeline, getPipeline } = require('./engine/pipeline');
3944
3959
  if (getPipeline(body.id)) return jsonReply(res, 409, { error: 'Pipeline already exists' });
3945
3960
  const pipeline = { id: body.id, title: body.title, stages: body.stages, trigger: body.trigger || {}, enabled: body.enabled !== false };
3961
+ if (Array.isArray(body.monitoredResources) && body.monitoredResources.length > 0) pipeline.monitoredResources = body.monitoredResources;
3946
3962
  savePipeline(pipeline);
3947
3963
  invalidateStatusCache();
3948
3964
  return jsonReply(res, 200, { ok: true, id: pipeline.id });
3949
3965
  }},
3950
- { method: 'POST', path: '/api/pipelines/update', desc: 'Update a pipeline', params: 'id, title?, stages?, trigger?, enabled?', handler: async (req, res) => {
3966
+ { method: 'POST', path: '/api/pipelines/update', desc: 'Update a pipeline', params: 'id, title?, stages?, trigger?, enabled?, monitoredResources?', handler: async (req, res) => {
3951
3967
  const body = await readBody(req);
3952
3968
  if (!body.id) return jsonReply(res, 400, { error: 'id required' });
3953
3969
  const { getPipeline, savePipeline } = require('./engine/pipeline');
@@ -3957,6 +3973,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3957
3973
  if (body.stages !== undefined) pipeline.stages = body.stages;
3958
3974
  if (body.trigger !== undefined) pipeline.trigger = body.trigger;
3959
3975
  if (body.enabled !== undefined) pipeline.enabled = body.enabled;
3976
+ if (body.monitoredResources !== undefined) pipeline.monitoredResources = body.monitoredResources;
3960
3977
  savePipeline(pipeline);
3961
3978
  invalidateStatusCache();
3962
3979
  return jsonReply(res, 200, { ok: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.592",
3
+ "version": "0.1.594",
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"