@yemi33/minions 0.1.225 → 0.1.227
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 +5 -1
- package/dashboard/js/render-pipelines.js +50 -51
- package/engine/cooldown.js +51 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -64,6 +64,54 @@ function _collectRunArtifacts(run) {
|
|
|
64
64
|
return { merged: merged, total: total };
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Build a segmented progress bar from pipeline stages and a run.
|
|
69
|
+
* @param {Array} stages - pipeline stage definitions
|
|
70
|
+
* @param {Object} run - the run object containing stages status map
|
|
71
|
+
* @param {Object} [options] - optional config: { height: '8px', detailLabel: true }
|
|
72
|
+
* @returns {string} HTML string for the progress bar
|
|
73
|
+
*/
|
|
74
|
+
function _buildProgressBar(stages, run, options) {
|
|
75
|
+
var totalStages = stages.length;
|
|
76
|
+
var completedCount = 0;
|
|
77
|
+
var runningCount = 0;
|
|
78
|
+
var failedCount = 0;
|
|
79
|
+
stages.forEach(function(s) {
|
|
80
|
+
var st = run.stages?.[s.id]?.status;
|
|
81
|
+
if (st === 'completed') completedCount++;
|
|
82
|
+
else if (st === 'running') runningCount++;
|
|
83
|
+
else if (st === 'failed') failedCount++;
|
|
84
|
+
});
|
|
85
|
+
var pct = Math.round((completedCount / totalStages) * 100);
|
|
86
|
+
|
|
87
|
+
var segments = stages.map(function(s) {
|
|
88
|
+
var st = run.stages?.[s.id]?.status || 'pending';
|
|
89
|
+
var cls = st === 'completed' ? 'complete' : st === 'running' ? 'running' : st === 'failed' ? 'failed' : st === 'waiting-human' ? 'waiting' : 'pending';
|
|
90
|
+
return '<div class="pl-prog-seg ' + cls + '" style="width:' + (100 / totalStages) + '%" title="' + escHtml(s.id) + ': ' + st + '"></div>';
|
|
91
|
+
}).join('');
|
|
92
|
+
|
|
93
|
+
var barStyle = options && options.height ? ' style="height:' + options.height + '"' : '';
|
|
94
|
+
|
|
95
|
+
var label;
|
|
96
|
+
if (options && options.detailLabel) {
|
|
97
|
+
label = '<span style="font-weight:600;color:' + (pct === 100 ? 'var(--green)' : failedCount ? 'var(--red)' : 'var(--blue)') + '">' + pct + '% complete</span> <span style="color:var(--muted)">(' + completedCount + '/' + totalStages + ' stages)</span>';
|
|
98
|
+
} else {
|
|
99
|
+
var statusParts = [];
|
|
100
|
+
if (completedCount) statusParts.push(completedCount + ' done');
|
|
101
|
+
if (runningCount) statusParts.push(runningCount + ' running');
|
|
102
|
+
if (failedCount) statusParts.push(failedCount + ' failed');
|
|
103
|
+
var remaining = totalStages - completedCount - runningCount - failedCount;
|
|
104
|
+
if (remaining > 0) statusParts.push(remaining + ' pending');
|
|
105
|
+
label = '<span style="font-weight:600;color:' + (pct === 100 ? 'var(--green)' : failedCount ? 'var(--red)' : 'var(--blue)') + '">' + pct + '%</span>' +
|
|
106
|
+
'<span style="color:var(--muted)">' + statusParts.join(' \u00b7 ') + '</span>';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return '<div class="pl-progress-wrap">' +
|
|
110
|
+
'<div class="pl-progress-bar"' + barStyle + '>' + segments + '</div>' +
|
|
111
|
+
'<div class="pl-progress-label">' + label + '</div>' +
|
|
112
|
+
'</div>';
|
|
113
|
+
}
|
|
114
|
+
|
|
67
115
|
function renderPipelines(pipelines) {
|
|
68
116
|
_pipelinesData = pipelines || [];
|
|
69
117
|
const el = document.getElementById('pipelines-content');
|
|
@@ -95,39 +143,7 @@ function renderPipelines(pipelines) {
|
|
|
95
143
|
var progressHtml = '';
|
|
96
144
|
var displayRun = activeRun || lastRun;
|
|
97
145
|
if (displayRun && (p.stages || []).length > 0) {
|
|
98
|
-
|
|
99
|
-
var completedCount = 0;
|
|
100
|
-
var runningCount = 0;
|
|
101
|
-
var failedCount = 0;
|
|
102
|
-
(p.stages || []).forEach(function(s) {
|
|
103
|
-
var st = displayRun.stages?.[s.id]?.status;
|
|
104
|
-
if (st === 'completed') completedCount++;
|
|
105
|
-
else if (st === 'running') runningCount++;
|
|
106
|
-
else if (st === 'failed') failedCount++;
|
|
107
|
-
});
|
|
108
|
-
var pct = Math.round((completedCount / totalStages) * 100);
|
|
109
|
-
|
|
110
|
-
// Segmented progress bar — one segment per stage
|
|
111
|
-
var segments = (p.stages || []).map(function(s) {
|
|
112
|
-
var st = displayRun.stages?.[s.id]?.status || 'pending';
|
|
113
|
-
var cls = st === 'completed' ? 'complete' : st === 'running' ? 'running' : st === 'failed' ? 'failed' : st === 'waiting-human' ? 'waiting' : 'pending';
|
|
114
|
-
return '<div class="pl-prog-seg ' + cls + '" style="width:' + (100 / totalStages) + '%" title="' + escHtml(s.id) + ': ' + st + '"></div>';
|
|
115
|
-
}).join('');
|
|
116
|
-
|
|
117
|
-
var statusParts = [];
|
|
118
|
-
if (completedCount) statusParts.push(completedCount + ' done');
|
|
119
|
-
if (runningCount) statusParts.push(runningCount + ' running');
|
|
120
|
-
if (failedCount) statusParts.push(failedCount + ' failed');
|
|
121
|
-
var remaining = totalStages - completedCount - runningCount - failedCount;
|
|
122
|
-
if (remaining > 0) statusParts.push(remaining + ' pending');
|
|
123
|
-
|
|
124
|
-
progressHtml = '<div class="pl-progress-wrap">' +
|
|
125
|
-
'<div class="pl-progress-bar">' + segments + '</div>' +
|
|
126
|
-
'<div class="pl-progress-label">' +
|
|
127
|
-
'<span style="font-weight:600;color:' + (pct === 100 ? 'var(--green)' : failedCount ? 'var(--red)' : 'var(--blue)') + '">' + pct + '%</span>' +
|
|
128
|
-
'<span style="color:var(--muted)">' + statusParts.join(' \u00b7 ') + '</span>' +
|
|
129
|
-
'</div>' +
|
|
130
|
-
'</div>';
|
|
146
|
+
progressHtml = _buildProgressBar(p.stages || [], displayRun);
|
|
131
147
|
}
|
|
132
148
|
|
|
133
149
|
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) + '\')">' +
|
|
@@ -166,24 +182,7 @@ function openPipelineDetail(id) {
|
|
|
166
182
|
// Stage detail with progress bar
|
|
167
183
|
var detailRun = activeRun || (p.runs || []).slice(-1)[0];
|
|
168
184
|
if (detailRun && (p.stages || []).length > 0) {
|
|
169
|
-
|
|
170
|
-
var ddone = 0, drun = 0, dfail = 0;
|
|
171
|
-
(p.stages || []).forEach(function(s) {
|
|
172
|
-
var st = detailRun.stages?.[s.id]?.status;
|
|
173
|
-
if (st === 'completed') ddone++;
|
|
174
|
-
else if (st === 'running') drun++;
|
|
175
|
-
else if (st === 'failed') dfail++;
|
|
176
|
-
});
|
|
177
|
-
var dpct = Math.round((ddone / dtotal) * 100);
|
|
178
|
-
var dsegs = (p.stages || []).map(function(s) {
|
|
179
|
-
var st = detailRun.stages?.[s.id]?.status || 'pending';
|
|
180
|
-
var cls = st === 'completed' ? 'complete' : st === 'running' ? 'running' : st === 'failed' ? 'failed' : st === 'waiting-human' ? 'waiting' : 'pending';
|
|
181
|
-
return '<div class="pl-prog-seg ' + cls + '" style="width:' + (100 / dtotal) + '%" title="' + escHtml(s.id) + ': ' + st + '"></div>';
|
|
182
|
-
}).join('');
|
|
183
|
-
html += '<div class="pl-progress-wrap">' +
|
|
184
|
-
'<div class="pl-progress-bar" style="height:8px">' + dsegs + '</div>' +
|
|
185
|
-
'<div class="pl-progress-label"><span style="font-weight:600;color:' + (dpct === 100 ? 'var(--green)' : dfail ? 'var(--red)' : 'var(--blue)') + '">' + dpct + '% complete</span> <span style="color:var(--muted)">(' + ddone + '/' + dtotal + ' stages)</span></div>' +
|
|
186
|
-
'</div>';
|
|
185
|
+
html += _buildProgressBar(p.stages || [], detailRun, { height: '8px', detailLabel: true });
|
|
187
186
|
}
|
|
188
187
|
html += '<h4 style="font-size:12px;color:var(--blue);margin:0">Stages</h4>';
|
|
189
188
|
(p.stages || []).forEach(function(s, i) {
|
package/engine/cooldown.js
CHANGED
|
@@ -7,10 +7,12 @@ const path = require('path');
|
|
|
7
7
|
const shared = require('./shared');
|
|
8
8
|
const queries = require('./queries');
|
|
9
9
|
|
|
10
|
+
const { createHash } = require('crypto');
|
|
10
11
|
const { safeJson, safeWrite, log } = shared;
|
|
11
12
|
const { ENGINE_DIR } = queries;
|
|
12
13
|
|
|
13
14
|
const COOLDOWN_PATH = path.join(ENGINE_DIR, 'cooldowns.json');
|
|
15
|
+
const PENDING_CONTEXTS_CAP = 10;
|
|
14
16
|
const dispatchCooldowns = new Map(); // key → { timestamp, failures }
|
|
15
17
|
|
|
16
18
|
function loadCooldowns() {
|
|
@@ -24,6 +26,35 @@ function loadCooldowns() {
|
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
log('info', `Loaded ${dispatchCooldowns.size} cooldowns from disk`);
|
|
29
|
+
// One-time purge of bloated pendingContexts on startup
|
|
30
|
+
purgeBloatedCooldowns();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Deduplicate and cap pendingContexts in all loaded cooldown entries. */
|
|
34
|
+
function purgeBloatedCooldowns() {
|
|
35
|
+
let totalRemoved = 0;
|
|
36
|
+
for (const [k, v] of dispatchCooldowns) {
|
|
37
|
+
if (!Array.isArray(v.pendingContexts) || v.pendingContexts.length <= 1) continue;
|
|
38
|
+
const seen = new Set();
|
|
39
|
+
const deduped = [];
|
|
40
|
+
for (const ctx of v.pendingContexts) {
|
|
41
|
+
const hash = _contentHash(ctx);
|
|
42
|
+
if (!seen.has(hash)) {
|
|
43
|
+
seen.add(hash);
|
|
44
|
+
deduped.push(ctx);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const before = v.pendingContexts.length;
|
|
48
|
+
// Apply FIFO cap after dedup — keep the most recent entries
|
|
49
|
+
v.pendingContexts = deduped.length > PENDING_CONTEXTS_CAP
|
|
50
|
+
? deduped.slice(deduped.length - PENDING_CONTEXTS_CAP)
|
|
51
|
+
: deduped;
|
|
52
|
+
totalRemoved += before - v.pendingContexts.length;
|
|
53
|
+
}
|
|
54
|
+
if (totalRemoved > 0) {
|
|
55
|
+
log('info', `Purged ${totalRemoved} duplicate/excess pendingContexts entries from cooldowns`);
|
|
56
|
+
saveCooldowns();
|
|
57
|
+
}
|
|
27
58
|
}
|
|
28
59
|
|
|
29
60
|
let _cooldownWriteTimer = null;
|
|
@@ -55,10 +86,26 @@ function setCooldown(key) {
|
|
|
55
86
|
saveCooldowns();
|
|
56
87
|
}
|
|
57
88
|
|
|
89
|
+
function _contentHash(content) {
|
|
90
|
+
const str = typeof content === 'string' ? content : JSON.stringify(content);
|
|
91
|
+
return createHash('sha256').update(str).digest('hex');
|
|
92
|
+
}
|
|
93
|
+
|
|
58
94
|
function setCooldownWithContext(key, context) {
|
|
59
95
|
const existing = dispatchCooldowns.get(key);
|
|
60
96
|
const pendingContexts = existing?.pendingContexts || [];
|
|
61
|
-
if (context)
|
|
97
|
+
if (context) {
|
|
98
|
+
// Dedup: only append if content differs from all existing entries
|
|
99
|
+
const newHash = _contentHash(context);
|
|
100
|
+
const isDuplicate = pendingContexts.some(c => _contentHash(c) === newHash);
|
|
101
|
+
if (!isDuplicate) {
|
|
102
|
+
pendingContexts.push(context);
|
|
103
|
+
// FIFO cap: drop oldest entries when exceeding cap
|
|
104
|
+
while (pendingContexts.length > PENDING_CONTEXTS_CAP) {
|
|
105
|
+
pendingContexts.shift();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
62
109
|
dispatchCooldowns.set(key, {
|
|
63
110
|
timestamp: Date.now(),
|
|
64
111
|
failures: existing?.failures || 0,
|
|
@@ -101,13 +148,16 @@ function isAlreadyDispatched(key) {
|
|
|
101
148
|
|
|
102
149
|
module.exports = {
|
|
103
150
|
COOLDOWN_PATH,
|
|
151
|
+
PENDING_CONTEXTS_CAP,
|
|
104
152
|
dispatchCooldowns,
|
|
105
153
|
loadCooldowns,
|
|
106
154
|
saveCooldowns,
|
|
155
|
+
purgeBloatedCooldowns,
|
|
107
156
|
isOnCooldown,
|
|
108
157
|
setCooldown,
|
|
109
158
|
setCooldownWithContext,
|
|
110
159
|
getCoalescedContexts,
|
|
111
160
|
setCooldownFailure,
|
|
112
161
|
isAlreadyDispatched,
|
|
162
|
+
_contentHash,
|
|
113
163
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.227",
|
|
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"
|