@yemi33/minions 0.1.2080 → 0.1.2082
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 +81 -64
- package/dashboard.js +8 -0
- package/engine/pipeline.js +29 -4
- package/package.json +1 -1
package/dashboard/js/refresh.js
CHANGED
|
@@ -104,6 +104,22 @@ const _sectionCacheVersions = {};
|
|
|
104
104
|
// into the ring-buffer entry then resets _lastChangedFlags to null so steady-
|
|
105
105
|
// state has no side effect. See "Refresh diagnostics" block below.
|
|
106
106
|
let _lastChangedFlags = null;
|
|
107
|
+
|
|
108
|
+
// Per-renderer isolation: a bare `renderX(...)` call that throws used to
|
|
109
|
+
// abort the rest of _processStatusUpdate, leaving the work-items and
|
|
110
|
+
// dispatch tables frozen until a hard refresh. _safeRender wraps each
|
|
111
|
+
// call so one throw can't take out the chain — every downstream renderer
|
|
112
|
+
// still runs and paints fresh DOM. Throws are logged to Console for
|
|
113
|
+
// triage but don't surface a UI banner (intentional — silent recovery is
|
|
114
|
+
// less disruptive than a red banner for what's typically a transient
|
|
115
|
+
// data-shape blip).
|
|
116
|
+
function _safeRender(name, fn) {
|
|
117
|
+
try { fn(); }
|
|
118
|
+
catch (e) {
|
|
119
|
+
// eslint-disable-next-line no-console
|
|
120
|
+
console.error('[render] ' + name + ' threw:', e);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
107
123
|
function _changed(key, value, version) {
|
|
108
124
|
var v = version == null ? (RENDER_VERSIONS[key] || 0) : version;
|
|
109
125
|
// Drop the stale-version entry so the cache doesn't grow unbounded across bumps.
|
|
@@ -294,12 +310,13 @@ function _processStatusUpdate(data) {
|
|
|
294
310
|
window._lastStatus = data;
|
|
295
311
|
|
|
296
312
|
|
|
297
|
-
// Render every section every tick
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
//
|
|
313
|
+
// Render every section every tick, each call ISOLATED via _safeRender so a
|
|
314
|
+
// single bad-data renderer can't abort the rest of the chain. Prior bug:
|
|
315
|
+
// an upstream throw (e.g. renderEngineStatus on a partial engine slice)
|
|
316
|
+
// propagated to refresh()'s outer catch, skipping every downstream
|
|
317
|
+
// renderer — including renderWorkItems below — until the offending record
|
|
318
|
+
// cycled out of the slim /api/status payload. Per-call try/catch keeps
|
|
319
|
+
// each renderer independent; throws log to Console (no UI banner).
|
|
303
320
|
//
|
|
304
321
|
// We KEEP the `_changed(...)` CALLS (their side effect of populating
|
|
305
322
|
// `_lastChangedFlags` is still load-bearing for the diag ring-buffer
|
|
@@ -308,80 +325,78 @@ function _processStatusUpdate(data) {
|
|
|
308
325
|
// value to gate the render. Each renderer is a contained DOM rewrite
|
|
309
326
|
// (~ a few KB of HTML); ~10–20 of them per 4s tick is well under one
|
|
310
327
|
// frame's budget.
|
|
311
|
-
//
|
|
312
|
-
// Gates intentionally kept: none on the render path. The render-versions
|
|
313
|
-
// bump path (RENDER_VERSIONS map + _changed's stringify cache) is still
|
|
314
|
-
// useful for the diag flag, just not as a render skip.
|
|
315
328
|
_changed('agents', data.agents);
|
|
316
|
-
renderAgents(data.agents);
|
|
317
|
-
cmdUpdateAgentList(data.agents);
|
|
329
|
+
_safeRender('agents', function() { renderAgents(data.agents); });
|
|
330
|
+
_safeRender('cmdUpdateAgentList', function() { cmdUpdateAgentList(data.agents); });
|
|
318
331
|
// prdProgress + prdPrs are captured together so both flags publish to
|
|
319
332
|
// the diag buffer; the renderer + cachePrdItems run unconditionally.
|
|
320
333
|
_changed('prdProgress', data.prdProgress);
|
|
321
334
|
_changed('prdPrs', data.pullRequests?.length);
|
|
322
|
-
renderPrdProgress(data.prdProgress);
|
|
323
|
-
_cachePrdItems(data.prdProgress);
|
|
335
|
+
_safeRender('prdProgress', function() { renderPrdProgress(data.prdProgress); });
|
|
336
|
+
_safeRender('cachePrdItems', function() { _cachePrdItems(data.prdProgress); });
|
|
324
337
|
_changed('inbox', data.inbox);
|
|
325
|
-
renderInbox(data.inbox || []);
|
|
338
|
+
_safeRender('inbox', function() { renderInbox(data.inbox || []); });
|
|
326
339
|
_changed('projects', data.projects);
|
|
327
|
-
cmdUpdateProjectList(data.projects || []);
|
|
328
|
-
renderProjects(data.projects || []);
|
|
340
|
+
_safeRender('cmdUpdateProjectList', function() { cmdUpdateProjectList(data.projects || []); });
|
|
341
|
+
_safeRender('projects', function() { renderProjects(data.projects || []); });
|
|
329
342
|
// FRE banner — safe to call every tick (idempotent + cheap). Pass the full
|
|
330
343
|
// status payload so the runtime-CLI explainer reads autoMode.defaultCli from
|
|
331
344
|
// THIS tick (window._lastStatus is hoisted above, but renderFre takes the
|
|
332
345
|
// payload directly to avoid the window-global indirection).
|
|
333
346
|
if (typeof renderFre === 'function') {
|
|
334
|
-
|
|
347
|
+
_safeRender('fre', function() { renderFre(data); });
|
|
335
348
|
}
|
|
336
349
|
_changed('notes', data.notes);
|
|
337
|
-
renderNotes(data.notes);
|
|
350
|
+
_safeRender('notes', function() { renderNotes(data.notes); });
|
|
338
351
|
_changed('prd', [data.prd, data.prdProgress]);
|
|
339
|
-
renderPrd(data.prd, data.prdProgress);
|
|
352
|
+
_safeRender('prd', function() { renderPrd(data.prd, data.prdProgress); });
|
|
340
353
|
// Capture prs + workItems change signals once — also reused by the cross-slice
|
|
341
354
|
// render triggers at the bottom of this function (F1/F3, W-mpgb0xbh000e3b86).
|
|
342
355
|
// _changed mutates _sectionCache so it must be called exactly once per key.
|
|
343
356
|
var _prsChanged = _changed('prs', data.pullRequests);
|
|
344
357
|
var _workItemsChanged = _changed('workItems', data.workItems);
|
|
345
|
-
renderPrs(data.pullRequests || []);
|
|
358
|
+
_safeRender('prs', function() { renderPrs(data.pullRequests || []); });
|
|
346
359
|
_changed('archivedPrds', data.archivedPrds);
|
|
347
|
-
renderArchiveButtons(data.archivedPrds || []);
|
|
360
|
+
_safeRender('archiveButtons', function() { renderArchiveButtons(data.archivedPrds || []); });
|
|
348
361
|
_changed('engine', data.engine);
|
|
349
|
-
if (data.engine) renderEngineStatus(data.engine);
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
'<span>
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
362
|
+
if (data.engine) _safeRender('engineStatus', function() { renderEngineStatus(data.engine); });
|
|
363
|
+
_safeRender('engineQuickStats', function() {
|
|
364
|
+
var qs = document.getElementById('engine-quick-stats');
|
|
365
|
+
if (qs && data.engine) {
|
|
366
|
+
var wt = data.engine.worktreeCount != null ? data.engine.worktreeCount : '-';
|
|
367
|
+
var pid = data.engine.pid || '-';
|
|
368
|
+
// W-mpnc4u8c001d9d6c — replace the dead "Tick: -" chip (control.json
|
|
369
|
+
// never carried a `tick` field) with a live "Next tick in Xs" countdown
|
|
370
|
+
// driven by engine.lastTickAt (stamped at the start of every tickInner)
|
|
371
|
+
// and engine.tickInterval (config, surfaced in the status payload).
|
|
372
|
+
// _updateNextTickChip below ticks the inner span every 1s without
|
|
373
|
+
// re-rendering this whole row.
|
|
374
|
+
_engineCountdown.lastTickAt = Number(data.engine.lastTickAt) || 0;
|
|
375
|
+
_engineCountdown.tickInterval = Number(data.engine.tickInterval) || 0;
|
|
376
|
+
_engineCountdown.engineState = data.engine.state || 'stopped';
|
|
377
|
+
// Feed the cadence ring buffer so the overshoot label can surface the
|
|
378
|
+
// observed tick-to-tick gap (W-mpodheao0006a37a).
|
|
379
|
+
_recordEngineTickObservation(_engineCountdown.lastTickAt);
|
|
380
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: composed from internal engine metrics (pid, lastTickAt/tickInterval, worktreeCount) and a literal id; no user data flows in
|
|
381
|
+
qs.innerHTML = '<span>PID: <b>' + pid + '</b></span>' +
|
|
382
|
+
'<span>Next tick in: <b id="engine-next-tick">' + _formatNextTickText() + '</b></span>' +
|
|
383
|
+
'<span>Worktrees: <b>' + wt + '</b></span>';
|
|
384
|
+
_startNextTickTicker();
|
|
385
|
+
}
|
|
386
|
+
});
|
|
372
387
|
_changed('version', data.version);
|
|
373
|
-
renderVersionBanner(data.version);
|
|
388
|
+
_safeRender('versionBanner', function() { renderVersionBanner(data.version); });
|
|
374
389
|
_changed('adoThrottle', data.adoThrottle);
|
|
375
|
-
renderAdoThrottleAlert(data.adoThrottle);
|
|
390
|
+
_safeRender('adoThrottle', function() { renderAdoThrottleAlert(data.adoThrottle); });
|
|
376
391
|
_changed('ghThrottle', data.ghThrottle);
|
|
377
|
-
renderGhThrottleAlert(data.ghThrottle);
|
|
392
|
+
_safeRender('ghThrottle', function() { renderGhThrottleAlert(data.ghThrottle); });
|
|
378
393
|
_changed('dispatch', data.dispatch);
|
|
379
|
-
renderDispatch(data.dispatch);
|
|
380
|
-
prunePrdRequeueState(window._lastWorkItems);
|
|
394
|
+
_safeRender('dispatch', function() { renderDispatch(data.dispatch); });
|
|
395
|
+
_safeRender('prunePrdRequeueState', function() { prunePrdRequeueState(window._lastWorkItems); });
|
|
381
396
|
_changed('engineLog', data.engineLog);
|
|
382
|
-
renderEngineLog(data.engineLog || []);
|
|
397
|
+
_safeRender('engineLog', function() { renderEngineLog(data.engineLog || []); });
|
|
383
398
|
_changed('metrics', data.metrics);
|
|
384
|
-
renderMetrics(data.metrics || {});
|
|
399
|
+
_safeRender('metrics', function() { renderMetrics(data.metrics || {}); });
|
|
385
400
|
// managed-processes panel — ETag-gated so unchanged ticks return 304 with
|
|
386
401
|
// no body (P-6e2a8b13). Sequenced BEFORE the keep-processes call below via
|
|
387
402
|
// .then() so the keep renderer reads a populated managed-PID cache for
|
|
@@ -400,21 +415,23 @@ function _processStatusUpdate(data) {
|
|
|
400
415
|
.catch(function () { /* keep render even if managed fetch failed — getLastItems() returns the last good cache (or []) */ })
|
|
401
416
|
.then(function () { try { renderKeepProcesses(); } catch {} });
|
|
402
417
|
}
|
|
403
|
-
renderWorkItems(data.workItems || []);
|
|
418
|
+
_safeRender('workItems', function() { renderWorkItems(data.workItems || []); });
|
|
404
419
|
_changed('skills', data.skills);
|
|
405
|
-
renderSkills(data.skills || []);
|
|
420
|
+
_safeRender('skills', function() { renderSkills(data.skills || []); });
|
|
406
421
|
_changed('mcpServers', data.mcpServers);
|
|
407
|
-
renderMcpServers(data.mcpServers || []);
|
|
422
|
+
_safeRender('mcpServers', function() { renderMcpServers(data.mcpServers || []); });
|
|
408
423
|
_changed('schedules', data.schedules);
|
|
409
|
-
renderSchedules(data.schedules || []);
|
|
424
|
+
_safeRender('schedules', function() { renderSchedules(data.schedules || []); });
|
|
410
425
|
_changed('watches', data.watches);
|
|
411
|
-
renderWatches(data.watches || []);
|
|
426
|
+
_safeRender('watches', function() { renderWatches(data.watches || []); });
|
|
412
427
|
_changed('meetings', data.meetings);
|
|
413
|
-
renderMeetings(data.meetings || []);
|
|
428
|
+
_safeRender('meetings', function() { renderMeetings(data.meetings || []); });
|
|
414
429
|
_changed('pipelines', data.pipelines);
|
|
415
|
-
if (typeof renderPipelines === 'function')
|
|
430
|
+
if (typeof renderPipelines === 'function') {
|
|
431
|
+
_safeRender('pipelines', function() { renderPipelines(data.pipelines || []); });
|
|
432
|
+
}
|
|
416
433
|
_changed('pinned', data.pinned);
|
|
417
|
-
renderPinned(data.pinned || []);
|
|
434
|
+
_safeRender('pinned', function() { renderPinned(data.pinned || []); });
|
|
418
435
|
// Sidebar counts (cheap)
|
|
419
436
|
const swi = document.getElementById('sidebar-wi');
|
|
420
437
|
if (swi) swi.textContent = (data.workItems || []).length || '';
|
|
@@ -430,8 +447,8 @@ function _processStatusUpdate(data) {
|
|
|
430
447
|
// and after every kb-sweep (engine/queries.js _kbCache / kb-sweep.js).
|
|
431
448
|
// Previously throttled to every 3rd cycle (~12s) — see W-mphfb6ss000a3b9e
|
|
432
449
|
// for the cadence audit + Playwright coverage.
|
|
433
|
-
refreshKnowledgeBase();
|
|
434
|
-
refreshPlans();
|
|
450
|
+
_safeRender('refreshKnowledgeBase', function() { refreshKnowledgeBase(); });
|
|
451
|
+
_safeRender('refreshPlans', function() { refreshPlans(); });
|
|
435
452
|
|
|
436
453
|
// Cross-slice render triggers (F1/F3, W-mpgb0xbh000e3b86): renderPrs reads
|
|
437
454
|
// window._lastWorkItems for the +N follow-up chip count and derivePlanStatus
|
|
@@ -444,14 +461,14 @@ function _processStatusUpdate(data) {
|
|
|
444
461
|
// F1: only the work-item slice moved this tick — renderPrs wasn't called
|
|
445
462
|
// above, so the +N follow-up chip would otherwise stay stale until the
|
|
446
463
|
// next PR mutation.
|
|
447
|
-
renderPrs(data.pullRequests || []);
|
|
464
|
+
_safeRender('prs:cross-slice', function() { renderPrs(data.pullRequests || []); });
|
|
448
465
|
}
|
|
449
466
|
if ((_workItemsChanged || _prsChanged) && Array.isArray(window._lastPlans) && typeof renderPlans === 'function') {
|
|
450
467
|
// F3: derivePlanStatus + _renderVerifyBadge derive from pullRequests +
|
|
451
468
|
// workItems. Re-render against cached plans so plan status flips within
|
|
452
469
|
// one /api/status tick (~4s) instead of one refreshPlans poll. No-op
|
|
453
470
|
// until _lastPlans is populated.
|
|
454
|
-
renderPlans(window._lastPlans);
|
|
471
|
+
_safeRender('plans:cross-slice', function() { renderPlans(window._lastPlans); });
|
|
455
472
|
}
|
|
456
473
|
|
|
457
474
|
// Sidebar activity indicators — show red dot on pages with new activity
|
package/dashboard.js
CHANGED
|
@@ -5593,6 +5593,9 @@ const server = http.createServer(async (req, res) => {
|
|
|
5593
5593
|
try {
|
|
5594
5594
|
const body = await readBody(req);
|
|
5595
5595
|
if (!body.source || !body.itemId) return jsonReply(res, 400, { error: 'source and itemId required' });
|
|
5596
|
+
// Reject markdown filenames — mutateJsonFileLocked below silently overwrites
|
|
5597
|
+
// the file with JSON, destroying source plans (see /api/plans/approve trap).
|
|
5598
|
+
if (!body.source.endsWith('.json')) return jsonReply(res, 400, { error: 'expected a PRD JSON filename in `source` (got `' + body.source + '`). Pass prd/<plan>.json, not plans/<plan>.md.' });
|
|
5596
5599
|
const planPath = resolvePlanPath(body.source);
|
|
5597
5600
|
if (!fs.existsSync(planPath)) return jsonReply(res, 404, { error: 'plan file not found' });
|
|
5598
5601
|
// Pre-check: verify item exists before taking the lock
|
|
@@ -5649,6 +5652,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
5649
5652
|
try {
|
|
5650
5653
|
const body = await readBody(req);
|
|
5651
5654
|
if (!body.source || !body.itemId) return jsonReply(res, 400, { error: 'source and itemId required' });
|
|
5655
|
+
if (!body.source.endsWith('.json')) return jsonReply(res, 400, { error: 'expected a PRD JSON filename in `source` (got `' + body.source + '`). Pass prd/<plan>.json, not plans/<plan>.md.' });
|
|
5652
5656
|
const planPath = resolvePlanPath(body.source);
|
|
5653
5657
|
if (!fs.existsSync(planPath)) return jsonReply(res, 404, { error: 'plan file not found' });
|
|
5654
5658
|
let removed = false;
|
|
@@ -6381,6 +6385,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
6381
6385
|
try {
|
|
6382
6386
|
const body = await readBody(req);
|
|
6383
6387
|
if (!body.file) return jsonReply(res, 400, { error: 'file required' });
|
|
6388
|
+
if (!body.file.endsWith('.json')) return jsonReply(res, 400, { error: 'expected a PRD JSON filename (got `' + body.file + '`). To approve a plan, pass prd/<plan>.json, not the source plans/<plan>.md.' });
|
|
6384
6389
|
const planPath = resolvePlanPath(body.file);
|
|
6385
6390
|
let wasStale = false;
|
|
6386
6391
|
const plan = mutateJsonFileLocked(planPath, (data) => {
|
|
@@ -6474,6 +6479,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
6474
6479
|
try {
|
|
6475
6480
|
const body = await readBody(req);
|
|
6476
6481
|
if (!body.file) return jsonReply(res, 400, { error: 'file required' });
|
|
6482
|
+
if (!body.file.endsWith('.json')) return jsonReply(res, 400, { error: 'expected a PRD JSON filename (got `' + body.file + '`). Pass prd/<plan>.json, not the source plans/<plan>.md.' });
|
|
6477
6483
|
const planPath = resolvePlanPath(body.file);
|
|
6478
6484
|
mutateJsonFileLocked(planPath, (plan) => {
|
|
6479
6485
|
if (!plan || Array.isArray(plan) || typeof plan !== 'object') plan = {};
|
|
@@ -6624,6 +6630,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
6624
6630
|
try {
|
|
6625
6631
|
const body = await readBody(req);
|
|
6626
6632
|
if (!body.file) return jsonReply(res, 400, { error: 'file required' });
|
|
6633
|
+
if (!body.file.endsWith('.json')) return jsonReply(res, 400, { error: 'expected a PRD JSON filename (got `' + body.file + '`). Pass prd/<plan>.json, not the source plans/<plan>.md.' });
|
|
6627
6634
|
const planPath = resolvePlanPath(body.file);
|
|
6628
6635
|
const plan = mutateJsonFileLocked(planPath, (data) => {
|
|
6629
6636
|
if (!data || Array.isArray(data) || typeof data !== 'object') data = {};
|
|
@@ -6882,6 +6889,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
6882
6889
|
try {
|
|
6883
6890
|
const body = await readBody(req);
|
|
6884
6891
|
if (!body.file || !body.feedback) return jsonReply(res, 400, { error: 'file and feedback required' });
|
|
6892
|
+
if (!body.file.endsWith('.json')) return jsonReply(res, 400, { error: 'expected a PRD JSON filename (got `' + body.file + '`). Pass prd/<plan>.json, not the source plans/<plan>.md.' });
|
|
6885
6893
|
const planPath = resolvePlanPath(body.file);
|
|
6886
6894
|
const plan = mutateJsonFileLocked(planPath, (data) => {
|
|
6887
6895
|
if (!data || Array.isArray(data) || typeof data !== 'object') data = {};
|
package/engine/pipeline.js
CHANGED
|
@@ -299,11 +299,19 @@ function evaluateCondition(condition, ctx) {
|
|
|
299
299
|
|
|
300
300
|
switch (cond.check) {
|
|
301
301
|
case 'runSucceeded': {
|
|
302
|
-
//
|
|
302
|
+
// P-bfa1e finding #21: tightened predicate.
|
|
303
|
+
// Previously this accepted PENDING as "succeeded" via `s.status === COMPLETED || s.status === PENDING`,
|
|
304
|
+
// which let `stopWhen`/`stop-pipeline` short-circuit fire while stages were still queued for the next
|
|
305
|
+
// dispatch cycle. A "succeeded" run is one where:
|
|
306
|
+
// 1. The run object exists (no run → nothing has succeeded).
|
|
307
|
+
// 2. At least one stage exists (vacuous-truth empty `.every()` would otherwise lie about success).
|
|
308
|
+
// 3. EVERY stage is COMPLETED — RUNNING/PENDING/WAITING_HUMAN/PAUSED/FAILED/STOPPED all fail the check.
|
|
309
|
+
// Any `stopWhen` or condition-stage `stop-pipeline` consumer wired to `runSucceeded` now sees `false`
|
|
310
|
+
// until the run is fully done, so it cannot prematurely terminate the pipeline.
|
|
303
311
|
if (!run) return false;
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
);
|
|
312
|
+
const stages = Object.values(run.stages || {});
|
|
313
|
+
if (stages.length === 0) return false;
|
|
314
|
+
return stages.every(s => s && s.status === PIPELINE_STATUS.COMPLETED);
|
|
307
315
|
}
|
|
308
316
|
case 'noFailedItems': {
|
|
309
317
|
// True when all work items created by the pipeline are done (not failed)
|
|
@@ -1156,6 +1164,23 @@ async function discoverPipelineWork(config) {
|
|
|
1156
1164
|
// Condition stage signaled pipeline stop — complete the run immediately
|
|
1157
1165
|
if (result._stopPipeline) {
|
|
1158
1166
|
completeRun(pipeline.id, activeRun.runId, PIPELINE_STATUS.STOPPED);
|
|
1167
|
+
// P-bfa1e finding #24: the local `activeRun` reference still has
|
|
1168
|
+
// `status === RUNNING` because we captured it at L980 before any
|
|
1169
|
+
// completion writes. The post-loop `allComplete` block at L1138
|
|
1170
|
+
// guards with `activeRun.status !== PIPELINE_STATUS.STOPPED`
|
|
1171
|
+
// before re-calling `completeRun(..., COMPLETED)` — if we leave
|
|
1172
|
+
// the stale RUNNING status in place that guard fails and we
|
|
1173
|
+
// clobber the STOPPED status with COMPLETED. Refresh the local
|
|
1174
|
+
// reference from disk so downstream reads in this same tick see
|
|
1175
|
+
// STOPPED. `getActiveRun` excludes terminal runs, so fall back
|
|
1176
|
+
// to a defensive in-place status patch when the refresh comes
|
|
1177
|
+
// back empty.
|
|
1178
|
+
const refreshed = getActiveRun(pipeline.id);
|
|
1179
|
+
if (refreshed) {
|
|
1180
|
+
activeRun = refreshed;
|
|
1181
|
+
} else {
|
|
1182
|
+
activeRun.status = PIPELINE_STATUS.STOPPED;
|
|
1183
|
+
}
|
|
1159
1184
|
allComplete = true;
|
|
1160
1185
|
break;
|
|
1161
1186
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2082",
|
|
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"
|