@yemi33/minions 0.1.2051 → 0.1.2052

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.
@@ -73,7 +73,7 @@ const RENDER_VERSIONS = {
73
73
  prd: 1,
74
74
  prs: 2,
75
75
  archivedPrds: 1,
76
- engine: 2,
76
+ engine: 3,
77
77
  version: 1,
78
78
  adoThrottle: 1,
79
79
  ghThrottle: 1,
@@ -147,6 +147,47 @@ function _formatCcDrawerLabel(autoMode) {
147
147
  return runtimeLabel + (model ? ' (' + model + ')' : '') + '-powered. Full minions context. Enter to send, Shift+Enter for newline.';
148
148
  }
149
149
 
150
+ // W-mpnc4u8c001d9d6c — #engine-quick-stats "Next tick in Xs" countdown.
151
+ //
152
+ // Origin is engine.lastTickAt (stamped by tickInner at the start of every
153
+ // tick, see engine.js) plus engine.tickInterval (server-side config, surfaced
154
+ // via _buildStatusFastState). The chip updates every 1s without re-rendering
155
+ // the surrounding row — mirrors the _tickAgentRuntimes pattern in
156
+ // dashboard/js/render-agents.js. The countdown clamps at 0 and shows "due"
157
+ // when the engine is mid-tick (PR polls etc. can push tick-to-tick wall time
158
+ // past the nominal interval). When the engine isn't running, show "—" so a
159
+ // stale lastTickAt doesn't render a ticking countdown the engine can't honor.
160
+ var _engineCountdown = { lastTickAt: 0, tickInterval: 0, engineState: 'stopped' };
161
+ var _engineNextTickTimer = null;
162
+
163
+ function _formatNextTickText() {
164
+ var state = _engineCountdown.engineState;
165
+ if (state !== 'running' && state !== 'stopping') return '—';
166
+ var last = _engineCountdown.lastTickAt;
167
+ var interval = _engineCountdown.tickInterval;
168
+ if (!last || !interval) return '—';
169
+ var remainingMs = last + interval - Date.now();
170
+ if (remainingMs <= 0) return 'due';
171
+ return Math.ceil(remainingMs / 1000) + 's';
172
+ }
173
+
174
+ function _updateNextTickChip() {
175
+ var el = document.getElementById('engine-next-tick');
176
+ if (!el) {
177
+ if (_engineNextTickTimer) { clearInterval(_engineNextTickTimer); _engineNextTickTimer = null; }
178
+ return;
179
+ }
180
+ var next = _formatNextTickText();
181
+ if (el.textContent !== next) el.textContent = next;
182
+ }
183
+
184
+ function _startNextTickTicker() {
185
+ // Idempotent — refresh.js re-runs every 4s and reseats the data on the
186
+ // shared _engineCountdown; we don't want compounding intervals.
187
+ if (_engineNextTickTimer) return;
188
+ _engineNextTickTimer = setInterval(_updateNextTickChip, 1000);
189
+ }
190
+
150
191
  function _processStatusUpdate(data) {
151
192
  // Detect fresh install — clear stale browser state AND reload so module-scoped
152
193
  // JS caches (_sectionCache, _prevCounts, _managedProcessesLastItems,
@@ -246,10 +287,21 @@ function _processStatusUpdate(data) {
246
287
  var qs = document.getElementById('engine-quick-stats');
247
288
  if (qs && data.engine) {
248
289
  var wt = data.engine.worktreeCount != null ? data.engine.worktreeCount : '-';
249
- var tick = data.engine.tick || '-';
250
290
  var pid = data.engine.pid || '-';
251
- // eslint-disable-next-line no-unsanitized/property -- reason: composed from internal engine metrics (pid, tick, worktreeCount; no user data flows in)
252
- qs.innerHTML = '<span>PID: <b>' + pid + '</b></span><span>Tick: <b>' + tick + '</b></span><span>Worktrees: <b>' + wt + '</b></span>';
291
+ // W-mpnc4u8c001d9d6c replace the dead "Tick: -" chip (control.json
292
+ // never carried a `tick` field) with a live "Next tick in Xs" countdown
293
+ // driven by engine.lastTickAt (stamped at the start of every tickInner)
294
+ // and engine.tickInterval (config, surfaced in the status payload).
295
+ // _updateNextTickChip below ticks the inner span every 1s without
296
+ // re-rendering this whole row.
297
+ _engineCountdown.lastTickAt = Number(data.engine.lastTickAt) || 0;
298
+ _engineCountdown.tickInterval = Number(data.engine.tickInterval) || 0;
299
+ _engineCountdown.engineState = data.engine.state || 'stopped';
300
+ // 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
301
+ qs.innerHTML = '<span>PID: <b>' + pid + '</b></span>' +
302
+ '<span>Next tick in: <b id="engine-next-tick">' + _formatNextTickText() + '</b></span>' +
303
+ '<span>Worktrees: <b>' + wt + '</b></span>';
304
+ _startNextTickTicker();
253
305
  }
254
306
  }
255
307
  if (_changed('version', data.version)) renderVersionBanner(data.version);
package/dashboard.js CHANGED
@@ -1730,6 +1730,11 @@ function _buildStatusFastState() {
1730
1730
  // possibly-cached payload (false-positive banner regression #2754).
1731
1731
  heartbeatAgeMs: engineState.heartbeat ? hbAge : null,
1732
1732
  heartbeatStale: !!(engineState.heartbeat && hbAge > ENGINE_HEARTBEAT_STALE_MS),
1733
+ // W-mpnc4u8c001d9d6c — Surface the tick cadence so the client's
1734
+ // #engine-quick-stats "Next tick in Xs" countdown reads the SAME value
1735
+ // the engine actually uses (Settings parity rule). Paired with
1736
+ // control.lastTickAt (above) stamped at the start of every tickInner.
1737
+ tickInterval: Number(CONFIG?.engine?.tickInterval) || shared.ENGINE_DEFAULTS.tickInterval,
1733
1738
  },
1734
1739
  adoThrottle: ado.getAdoThrottleState(),
1735
1740
  ghThrottle: gh.getGhThrottleState(),
package/engine.js CHANGED
@@ -6522,6 +6522,17 @@ async function tickInner() {
6522
6522
  // Per-phase guards inside the rest of tickInner are sub-task -b's scope.
6523
6523
  if (_isTickStale(myGeneration)) return;
6524
6524
 
6525
+ // W-mpnc4u8c001d9d6c — Stamp `lastTickAt` on every tickInner entry so the
6526
+ // dashboard's #engine-quick-stats "Next tick in Xs" countdown has a reliable
6527
+ // anchor (control.json carries no separate tick-counter field). Synchronous
6528
+ // mutator, no awaits inside the lock — mirrors the writer contract used by
6529
+ // engine/cli.js#writeHeartbeatNow. Distinct from control.heartbeat: this
6530
+ // advances once per tick cadence, not on the 15s heartbeat cadence, so the
6531
+ // client can render an accurate "next tick" countdown without coupling to
6532
+ // the staleness signal.
6533
+ try { shared.mutateControl(c => { c.lastTickAt = Date.now(); return c; }); }
6534
+ catch (e) { log('warn', `lastTickAt write: ${e.message}`); }
6535
+
6525
6536
  const config = getConfig();
6526
6537
  tickCount++;
6527
6538
  const now = Date.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2051",
3
+ "version": "0.1.2052",
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"