@yemi33/minions 0.1.2014 → 0.1.2016
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/dashboard/js/refresh.js
CHANGED
|
@@ -74,6 +74,17 @@ function _processStatusUpdate(data) {
|
|
|
74
74
|
const threshEl = document.getElementById('inbox-threshold');
|
|
75
75
|
if (threshEl && data.autoMode?.inboxThreshold) threshEl.textContent = data.autoMode.inboxThreshold;
|
|
76
76
|
|
|
77
|
+
// Publish window._last* snapshots BEFORE any renderer runs. Several renderers
|
|
78
|
+
// (renderProjects, renderPrd, renderPrs, refreshPlans, and others) read
|
|
79
|
+
// window._lastStatus / window._lastWorkItems / window._lastDispatch during
|
|
80
|
+
// their render path. If we assign these after the renderer calls, every tick
|
|
81
|
+
// consumes the PREVIOUS tick's globals — a one-tick (~4 s) staleness lag
|
|
82
|
+
// baked into the polling loop (W-mpgb0x81000cf8df, audit F4). No renderer in
|
|
83
|
+
// this function mutates `data`, so hoisting is safe.
|
|
84
|
+
window._lastDispatch = data.dispatch;
|
|
85
|
+
window._lastWorkItems = data.workItems || [];
|
|
86
|
+
window._lastStatus = data;
|
|
87
|
+
|
|
77
88
|
// Render only changed sections
|
|
78
89
|
if (_changed('agents', data.agents)) { renderAgents(data.agents); cmdUpdateAgentList(data.agents); }
|
|
79
90
|
if (_changed('prdProgress', data.prdProgress) || _changed('prdPrs', data.pullRequests?.length)) { renderPrdProgress(data.prdProgress); _cachePrdItems(data.prdProgress); }
|
|
@@ -81,13 +92,19 @@ function _processStatusUpdate(data) {
|
|
|
81
92
|
if (_changed('projects', data.projects)) { cmdUpdateProjectList(data.projects || []); renderProjects(data.projects || []); }
|
|
82
93
|
// FRE banner — safe to call every tick (idempotent + cheap). Pass the full
|
|
83
94
|
// status payload so the runtime-CLI explainer reads autoMode.defaultCli from
|
|
84
|
-
// THIS tick
|
|
95
|
+
// THIS tick (window._lastStatus is hoisted above, but renderFre takes the
|
|
96
|
+
// payload directly to avoid the window-global indirection).
|
|
85
97
|
if (typeof renderFre === 'function') {
|
|
86
98
|
try { renderFre(data); } catch { /* expected on first load */ }
|
|
87
99
|
}
|
|
88
100
|
if (_changed('notes', data.notes)) renderNotes(data.notes);
|
|
89
101
|
if (_changed('prd', [data.prd, data.prdProgress])) renderPrd(data.prd, data.prdProgress);
|
|
90
|
-
|
|
102
|
+
// Capture prs + workItems change signals once — also reused by the cross-slice
|
|
103
|
+
// render triggers at the bottom of this function (F1/F3, W-mpgb0xbh000e3b86).
|
|
104
|
+
// _changed mutates _sectionCache so it must be called exactly once per key.
|
|
105
|
+
var _prsChanged = _changed('prs', data.pullRequests);
|
|
106
|
+
var _workItemsChanged = _changed('workItems', data.workItems);
|
|
107
|
+
if (_prsChanged) renderPrs(data.pullRequests || []);
|
|
91
108
|
if (_changed('archivedPrds', data.archivedPrds)) renderArchiveButtons(data.archivedPrds || []);
|
|
92
109
|
if (_changed('engine', data.engine)) {
|
|
93
110
|
renderEngineStatus(data.engine);
|
|
@@ -103,9 +120,6 @@ function _processStatusUpdate(data) {
|
|
|
103
120
|
if (_changed('adoThrottle', data.adoThrottle)) renderAdoThrottleAlert(data.adoThrottle);
|
|
104
121
|
if (_changed('ghThrottle', data.ghThrottle)) renderGhThrottleAlert(data.ghThrottle);
|
|
105
122
|
if (_changed('dispatch', data.dispatch)) renderDispatch(data.dispatch);
|
|
106
|
-
window._lastDispatch = data.dispatch;
|
|
107
|
-
window._lastWorkItems = data.workItems || [];
|
|
108
|
-
window._lastStatus = data;
|
|
109
123
|
prunePrdRequeueState(window._lastWorkItems);
|
|
110
124
|
if (_changed('engineLog', data.engineLog)) renderEngineLog(data.engineLog || []);
|
|
111
125
|
if (_changed('metrics', data.metrics)) renderMetrics(data.metrics || {});
|
|
@@ -127,7 +141,7 @@ function _processStatusUpdate(data) {
|
|
|
127
141
|
.catch(function () { /* keep render even if managed fetch failed — getLastItems() returns the last good cache (or []) */ })
|
|
128
142
|
.then(function () { try { renderKeepProcesses(); } catch {} });
|
|
129
143
|
}
|
|
130
|
-
if (
|
|
144
|
+
if (_workItemsChanged) renderWorkItems(data.workItems || []);
|
|
131
145
|
if (_changed('skills', data.skills)) renderSkills(data.skills || []);
|
|
132
146
|
if (_changed('mcpServers', data.mcpServers)) renderMcpServers(data.mcpServers || []);
|
|
133
147
|
if (_changed('schedules', data.schedules)) renderSchedules(data.schedules || []);
|
|
@@ -144,6 +158,27 @@ function _processStatusUpdate(data) {
|
|
|
144
158
|
if (!window._kbRefreshCount) window._kbRefreshCount = 0;
|
|
145
159
|
if (window._kbRefreshCount++ % 3 === 0) { refreshKnowledgeBase(); refreshPlans(); }
|
|
146
160
|
|
|
161
|
+
// Cross-slice render triggers (F1/F3, W-mpgb0xbh000e3b86): renderPrs reads
|
|
162
|
+
// window._lastWorkItems for the +N follow-up chip count and derivePlanStatus
|
|
163
|
+
// reads window._lastStatus.pullRequests for verify-follow-up reconciliation.
|
|
164
|
+
// When workItems OR pullRequests change, re-render the dependents against the
|
|
165
|
+
// freshest cached data — without waiting up to 12s for the next refreshPlans
|
|
166
|
+
// cycle (F3) or for a PR object to mutate (F1). Runs after the window._last*
|
|
167
|
+
// assignments above so the cached globals these renderers consult are fresh.
|
|
168
|
+
if (_workItemsChanged && !_prsChanged) {
|
|
169
|
+
// F1: only the work-item slice moved this tick — renderPrs wasn't called
|
|
170
|
+
// above, so the +N follow-up chip would otherwise stay stale until the
|
|
171
|
+
// next PR mutation.
|
|
172
|
+
renderPrs(data.pullRequests || []);
|
|
173
|
+
}
|
|
174
|
+
if ((_workItemsChanged || _prsChanged) && Array.isArray(window._lastPlans) && typeof renderPlans === 'function') {
|
|
175
|
+
// F3: derivePlanStatus + _renderVerifyBadge derive from pullRequests +
|
|
176
|
+
// workItems. Re-render against the cached plan list so plan status flips
|
|
177
|
+
// within one /api/status tick (~4s) instead of one refreshPlans poll
|
|
178
|
+
// (~12s). No-op when _lastPlans hasn't been populated yet (first load).
|
|
179
|
+
renderPlans(window._lastPlans);
|
|
180
|
+
}
|
|
181
|
+
|
|
147
182
|
// Sidebar activity indicators — show red dot on pages with new activity
|
|
148
183
|
try {
|
|
149
184
|
const changes = _detectPageChanges(data);
|
|
@@ -164,7 +199,15 @@ let _knownDashboardStartId = null;
|
|
|
164
199
|
// downstream consumers of `data`.
|
|
165
200
|
let _lastStatusEtag = null;
|
|
166
201
|
let _lastStatusData = null;
|
|
202
|
+
// In-flight gate (S5 — dashboard-refresh-audit). setInterval(refresh, 4000)
|
|
203
|
+
// can interleave two ticks inside _processStatusUpdate if a refresh takes
|
|
204
|
+
// >4 s (cold server cache + slow JSON + network blip). Skip the second tick
|
|
205
|
+
// when the first hasn't finished — the next interval fire picks up the
|
|
206
|
+
// fresh state anyway.
|
|
207
|
+
let _refreshInFlight = false;
|
|
167
208
|
async function refresh() {
|
|
209
|
+
if (_refreshInFlight) return;
|
|
210
|
+
_refreshInFlight = true;
|
|
168
211
|
try {
|
|
169
212
|
const headers = {};
|
|
170
213
|
if (_lastStatusEtag) headers['If-None-Match'] = _lastStatusEtag;
|
|
@@ -191,6 +234,7 @@ async function refresh() {
|
|
|
191
234
|
if (dashId) _knownDashboardStartId = dashId;
|
|
192
235
|
_processStatusUpdate(data);
|
|
193
236
|
} catch(e) { console.error('refresh error', e); }
|
|
237
|
+
finally { _refreshInFlight = false; }
|
|
194
238
|
}
|
|
195
239
|
|
|
196
240
|
refresh();
|
|
@@ -32,8 +32,16 @@ function _formatSweepElapsed(startedAt) {
|
|
|
32
32
|
|
|
33
33
|
async function refreshKnowledgeBase() {
|
|
34
34
|
try {
|
|
35
|
+
// Always update _kbData — cross-slice readers (e.g. command-center.js
|
|
36
|
+
// suggestion lookup) depend on it staying fresh even when the renderer is
|
|
37
|
+
// skipped (S1).
|
|
35
38
|
_kbData = await fetch('/api/knowledge').then(r => r.json());
|
|
36
|
-
|
|
39
|
+
// Skip the full kb-list/kb-tabs rewrite when /api/knowledge returned the
|
|
40
|
+
// same payload as last tick. Dedicated cache key 'kbPayload' avoids
|
|
41
|
+
// colliding with any other tracked slice (S1).
|
|
42
|
+
if (typeof _changed !== 'function' || _changed('kbPayload', _kbData)) {
|
|
43
|
+
renderKnowledgeBase();
|
|
44
|
+
}
|
|
37
45
|
} catch (e) { console.error('kb refresh:', e.message); }
|
|
38
46
|
}
|
|
39
47
|
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
let _pipelinesData = [];
|
|
4
4
|
let _pipelinePollId = null;
|
|
5
5
|
let _pipelinePollInterval = null;
|
|
6
|
+
const PIPELINE_PER_PAGE = 25;
|
|
7
|
+
let _pipelinePage = 0;
|
|
8
|
+
let _pipelineTotalPages = 1;
|
|
6
9
|
function _stopPipelinePoll() { if (_pipelinePollInterval) { clearInterval(_pipelinePollInterval); _pipelinePollInterval = null; } _pipelinePollId = null; }
|
|
7
10
|
|
|
8
11
|
/**
|
|
@@ -293,11 +296,18 @@ function renderPipelines(pipelines) {
|
|
|
293
296
|
if (!pipelines || pipelines.length === 0) {
|
|
294
297
|
countEl.textContent = '0';
|
|
295
298
|
el.innerHTML = '<p class="empty">No pipelines yet. Create one to chain stages like audit \u2192 meeting \u2192 plan \u2192 merge.</p>';
|
|
299
|
+
_pipelineTotalPages = 1;
|
|
296
300
|
return;
|
|
297
301
|
}
|
|
298
302
|
countEl.textContent = pipelines.length;
|
|
299
303
|
|
|
300
|
-
|
|
304
|
+
const totalPages = Math.ceil(pipelines.length / PIPELINE_PER_PAGE);
|
|
305
|
+
_pipelineTotalPages = totalPages;
|
|
306
|
+
if (_pipelinePage >= totalPages) _pipelinePage = Math.max(0, totalPages - 1);
|
|
307
|
+
const start = _pipelinePage * PIPELINE_PER_PAGE;
|
|
308
|
+
const pageItems = pipelines.slice(start, start + PIPELINE_PER_PAGE);
|
|
309
|
+
|
|
310
|
+
el.innerHTML = pageItems.map(function(p) {
|
|
301
311
|
const activeRun = _getPipelineActiveRun(p);
|
|
302
312
|
const lastRun = (p.runs || []).slice(-1)[0];
|
|
303
313
|
const statusColor = activeRun ? 'var(--blue)' : lastRun?.status === 'completed' ? 'var(--green)' : lastRun?.status === 'failed' ? 'var(--red)' : lastRun?.status === 'stopped' ? 'var(--yellow)' : 'var(--muted)';
|
|
@@ -336,6 +346,29 @@ function renderPipelines(pipelines) {
|
|
|
336
346
|
progressHtml +
|
|
337
347
|
'</div>';
|
|
338
348
|
}).join('');
|
|
349
|
+
|
|
350
|
+
if (pipelines.length > PIPELINE_PER_PAGE) {
|
|
351
|
+
el.insertAdjacentHTML('beforeend', '<div class="pr-pager">' +
|
|
352
|
+
'<span class="pr-page-info">Showing ' + (start + 1) + ' to ' + Math.min(start + PIPELINE_PER_PAGE, pipelines.length) + ' of ' + pipelines.length + '</span>' +
|
|
353
|
+
'<div class="pr-pager-btns">' +
|
|
354
|
+
'<button class="pr-pager-btn ' + (_pipelinePage === 0 ? 'disabled' : '') + '" onclick="_pipelinePrev()">Prev</button>' +
|
|
355
|
+
'<button class="pr-pager-btn ' + (_pipelinePage >= totalPages - 1 ? 'disabled' : '') + '" onclick="_pipelineNext()">Next</button>' +
|
|
356
|
+
'</div></div>');
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function _pipelinePrev() {
|
|
361
|
+
if (_pipelinePage > 0) {
|
|
362
|
+
_pipelinePage--;
|
|
363
|
+
renderPipelines(_pipelinesData);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function _pipelineNext() {
|
|
368
|
+
if (_pipelinePage < _pipelineTotalPages - 1) {
|
|
369
|
+
_pipelinePage++;
|
|
370
|
+
renderPipelines(_pipelinesData);
|
|
371
|
+
}
|
|
339
372
|
}
|
|
340
373
|
|
|
341
374
|
function openPipelineDetail(id) {
|
|
@@ -56,8 +56,16 @@ async function _submitCreatePlan(e) {
|
|
|
56
56
|
async function refreshPlans() {
|
|
57
57
|
try {
|
|
58
58
|
const plans = await fetch('/api/plans').then(r => r.json());
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
// Cross-slice readers (renderMeetings, plan modal, command-center, etc.)
|
|
60
|
+
// depend on _lastPlans staying current — assign BEFORE the change gate so
|
|
61
|
+
// they see fresh data even when the renderer is skipped (F2/F3).
|
|
62
|
+
window._lastPlans = plans;
|
|
63
|
+
// Skip the full plans-list innerHTML rewrite when /api/plans returned the
|
|
64
|
+
// same payload as last tick. Dedicated cache key 'plansPayload' avoids
|
|
65
|
+
// colliding with the 'plans' slice tracked by _processStatusUpdate (F6).
|
|
66
|
+
if (typeof _changed !== 'function' || _changed('plansPayload', plans)) {
|
|
67
|
+
renderPlans(plans);
|
|
68
|
+
}
|
|
61
69
|
} catch (e) { console.error('plans refresh:', e.message); }
|
|
62
70
|
}
|
|
63
71
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2016",
|
|
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"
|