@yemi33/minions 0.1.2055 → 0.1.2056

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: 3,
76
+ engine: 4,
77
77
  version: 1,
78
78
  adoThrottle: 1,
79
79
  ghThrottle: 1,
@@ -147,18 +147,57 @@ 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.
150
+ // W-mpnc4u8c001d9d6c / W-mpodheao0006a37a — #engine-quick-stats "Next tick in"
151
+ // countdown.
151
152
  //
152
153
  // Origin is engine.lastTickAt (stamped by tickInner at the start of every
153
154
  // tick, see engine.js) plus engine.tickInterval (server-side config, surfaced
154
155
  // via _buildStatusFastState). The chip updates every 1s without re-rendering
155
156
  // 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.
157
+ // dashboard/js/render-agents.js.
158
+ //
159
+ // Cadence semantics (W-mpodheao0006a37a): tickInterval is the MINIMUM gap
160
+ // between tick starts (engine.js#cli sets `setInterval(() => e.tick(), interval)`
161
+ // and engine.tick() short-circuits if a previous tick is still running). Slow
162
+ // ticks (PR polls, reconcilers) delay the next slot, so actual tick-to-tick
163
+ // gaps can exceed tickInterval. Once the countdown crosses zero the chip
164
+ // shows "running" (not "due" — the old label implied the engine was overdue,
165
+ // which lies about the floor-not-ceiling semantics). When we have ≥3
166
+ // observed deltas in _engineCadenceDeltas, the running label is augmented
167
+ // with the observed median cadence: "running (~Ns cadence)".
168
+ //
169
+ // When the engine isn't running, show "—" so a stale lastTickAt doesn't
170
+ // render a ticking countdown the engine can't honor.
160
171
  var _engineCountdown = { lastTickAt: 0, tickInterval: 0, engineState: 'stopped' };
161
172
  var _engineNextTickTimer = null;
173
+ // Ring buffer of the most recent observed tick-to-tick deltas (ms). Updated
174
+ // inside _processStatusUpdate whenever data.engine.lastTickAt advances. Used
175
+ // by _formatNextTickText to surface an observed cadence in the overshoot
176
+ // path. Capped at _engineCadenceMax entries (FIFO) so the median stays
177
+ // representative of recent behavior.
178
+ var _engineCadenceDeltas = [];
179
+ var _engineCadenceMax = 5;
180
+ var _engineCadenceLastSeenTickAt = 0;
181
+
182
+ function _recordEngineTickObservation(lastTickAt) {
183
+ if (!lastTickAt) return;
184
+ if (!_engineCadenceLastSeenTickAt) {
185
+ _engineCadenceLastSeenTickAt = lastTickAt;
186
+ return;
187
+ }
188
+ if (lastTickAt <= _engineCadenceLastSeenTickAt) return;
189
+ var delta = lastTickAt - _engineCadenceLastSeenTickAt;
190
+ _engineCadenceLastSeenTickAt = lastTickAt;
191
+ _engineCadenceDeltas.push(delta);
192
+ if (_engineCadenceDeltas.length > _engineCadenceMax) _engineCadenceDeltas.shift();
193
+ }
194
+
195
+ function _medianCadenceMs() {
196
+ if (_engineCadenceDeltas.length < 3) return 0;
197
+ var sorted = _engineCadenceDeltas.slice().sort(function(a, b) { return a - b; });
198
+ var mid = Math.floor(sorted.length / 2);
199
+ return sorted.length % 2 ? sorted[mid] : Math.round((sorted[mid - 1] + sorted[mid]) / 2);
200
+ }
162
201
 
163
202
  function _formatNextTickText() {
164
203
  var state = _engineCountdown.engineState;
@@ -167,8 +206,11 @@ function _formatNextTickText() {
167
206
  var interval = _engineCountdown.tickInterval;
168
207
  if (!last || !interval) return '—';
169
208
  var remainingMs = last + interval - Date.now();
170
- if (remainingMs <= 0) return 'due';
171
- return Math.ceil(remainingMs / 1000) + 's';
209
+ if (remainingMs > 0) return Math.ceil(remainingMs / 1000) + 's';
210
+ if (state !== 'running') return '';
211
+ var medianMs = _medianCadenceMs();
212
+ if (medianMs > 0) return 'running (~' + Math.round(medianMs / 1000) + 's cadence)';
213
+ return 'running';
172
214
  }
173
215
 
174
216
  function _updateNextTickChip() {
@@ -297,6 +339,9 @@ function _processStatusUpdate(data) {
297
339
  _engineCountdown.lastTickAt = Number(data.engine.lastTickAt) || 0;
298
340
  _engineCountdown.tickInterval = Number(data.engine.tickInterval) || 0;
299
341
  _engineCountdown.engineState = data.engine.state || 'stopped';
342
+ // Feed the cadence ring buffer so the overshoot label can surface the
343
+ // observed tick-to-tick gap (W-mpodheao0006a37a).
344
+ _recordEngineTickObservation(_engineCountdown.lastTickAt);
300
345
  // 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
346
  qs.innerHTML = '<span>PID: <b>' + pid + '</b></span>' +
302
347
  '<span>Next tick in: <b id="engine-next-tick">' + _formatNextTickText() + '</b></span>' +
@@ -693,6 +738,21 @@ async function refresh() {
693
738
 
694
739
  refresh();
695
740
  _syncPinsFromServer(); // Load server-side pins on startup
741
+ // W-mpmwxkrw000872ec — reconcile the font-size preference from the server
742
+ // once on cold load. The inline bootstrap in layout.html has already applied
743
+ // localStorage's value (if any) to avoid flash; this fetch promotes the
744
+ // server value when it differs (e.g. fresh browser, different machine).
745
+ (function _reconcileFontSizeFromServer() {
746
+ fetch('/api/settings').then(function(r) { return r.ok ? r.json() : null; }).then(function(data) {
747
+ if (!data || !data.engine) return;
748
+ var serverValue = data.engine.fontSize || 'small';
749
+ var localValue = 'small';
750
+ try { localValue = localStorage.getItem('minions:font-size') || 'small'; } catch (e) { /* private mode */ }
751
+ if (serverValue !== localValue && typeof window.applyFontSizePreference === 'function') {
752
+ window.applyFontSizePreference(serverValue);
753
+ }
754
+ }).catch(function() { /* offline / 4xx — keep localStorage value */ });
755
+ })();
696
756
 
697
757
  // Poll for status updates (SSE caused HTTP/1.1 connection exhaustion — CC fetch would fail)
698
758
  setInterval(refresh, 4000);
@@ -1,5 +1,17 @@
1
1
  // settings.js — Settings panel functions extracted from dashboard.html
2
2
 
3
+ // W-mpmwxkrw000872ec — single source of truth for applying the font-size
4
+ // preference at runtime. Mirrors the inline bootstrap script in
5
+ // layout.html (which runs before this file loads). Exposed on window so the
6
+ // refresh.js reconciler can call it after the first /api/settings fetch.
7
+ const FONT_SIZE_VALUES = ['small', 'medium', 'large', 'xlarge'];
8
+ function applyFontSizePreference(value) {
9
+ const v = FONT_SIZE_VALUES.includes(value) ? value : 'small';
10
+ try { document.documentElement.setAttribute('data-font-size', v); } catch (e) { /* no DOM */ }
11
+ try { localStorage.setItem('minions:font-size', v); } catch (e) { /* private mode / disabled */ }
12
+ }
13
+ try { window.applyFontSizePreference = applyFontSizePreference; } catch (e) { /* non-browser */ }
14
+
3
15
  let _settingsData = null;
4
16
  // Async runtime/model discovery can resolve out of order when the operator
5
17
  // flips between runtimes quickly. Without a per-target token, a slower Copilot
@@ -166,6 +178,29 @@ async function openSettings() {
166
178
  settingsField('Eval Max Cost', 'set-evalMaxCost', e.evalMaxCost === null || e.evalMaxCost === undefined ? '' : e.evalMaxCost, '$', 'USD ceiling per work item across all eval iterations (blank = no limit)') +
167
179
  '</div>';
168
180
 
181
+ // W-mpmwxkrw000872ec — Appearance pane. Hosts dashboard-wide visual
182
+ // preferences (font-size scale today; reserved for future theme picks).
183
+ // Kept narrow + featured near the rail top so the setting is discoverable
184
+ // rather than buried under generic Engine headers.
185
+ const paneAppearance =
186
+ '<h3>Appearance</h3>' +
187
+ '<div class="settings-pane-sub">Dashboard-wide visual preferences. Persisted in browser localStorage + server-side settings so they survive a reload from a cold cache.</div>' +
188
+ '<div class="settings-grid-2">' +
189
+ '<div data-search="font size scale appearance dashboard">' +
190
+ '<label style="font-size:10px;color:var(--muted);display:block;margin-bottom:2px">Font Size</label>' +
191
+ '<select id="set-fontSize" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px">' +
192
+ (function() {
193
+ var current = (e.fontSize || 'small');
194
+ var opts = [['small', 'Small (current default)'], ['medium', 'Medium'], ['large', 'Large'], ['xlarge', 'Extra Large']];
195
+ return opts.map(function(o) {
196
+ return '<option value="' + o[0] + '"' + (current === o[0] ? ' selected' : '') + '>' + o[1] + '</option>';
197
+ }).join('');
198
+ })() +
199
+ '</select>' +
200
+ '<div style="font-size:9px;color:var(--muted);margin-top:1px">Scales the whole dashboard. Persisted in browser + server.</div>' +
201
+ '</div>' +
202
+ '</div>';
203
+
169
204
  const projectsHtml = (data.projects || []).map(function(p) {
170
205
  // Cross-reference live /api/status to pull in the auto-detected
171
206
  // remoteDefaultBranch (read-only) — settings already has localPath +
@@ -230,7 +265,7 @@ async function openSettings() {
230
265
  '<div class="settings-pane-sub">How many agents run at once and how long they get before being killed for silence or runaway duration.</div>' +
231
266
  '<div class="settings-grid-2">' +
232
267
  settingsField('Max Concurrent Agents', 'set-maxConcurrent', e.maxConcurrent || 5, '', 'Max agents working simultaneously') +
233
- settingsField('Tick Interval', 'set-tickInterval', e.tickInterval || 60000, 'ms', 'How often the engine runs discovery + dispatch') +
268
+ settingsField('Min Tick Interval', 'set-tickInterval', e.tickInterval || 60000, 'ms', 'Minimum gap between tick starts. Slow ticks (PR polls, reconcilers) may delay the next slot see `running` indicator on /engine.') +
234
269
  settingsField('Agent Timeout', 'set-agentTimeout', e.agentTimeout || 18000000, 'ms', 'Kill agent after this duration') +
235
270
  settingsField('Heartbeat Timeout', 'set-heartbeatTimeout', e.heartbeatTimeout || 300000, 'ms', 'No output = dead after this') +
236
271
  settingsField('Idle Alert', 'set-idleAlertMinutes', e.idleAlertMinutes || 15, 'min', 'Alert after agent idle this long') +
@@ -378,6 +413,7 @@ async function openSettings() {
378
413
  const sections = [
379
414
  { id: 'runtime', label: 'Runtime & Models', featured: true, html: paneRuntime },
380
415
  { id: 'autofix', label: 'Auto-fix & Review Loop', featured: true, html: paneAutoFix },
416
+ { id: 'appearance', label: 'Appearance', html: paneAppearance },
381
417
  { id: 'projects', label: 'Projects', html: paneProjects },
382
418
  { id: 'polling', label: 'Polling', html: panePolling },
383
419
  { id: 'concurrency', label: 'Concurrency & Timeouts', html: paneConcurrency },
@@ -848,6 +884,9 @@ async function saveSettings() {
848
884
  copilotReasoningSummaries: !!document.getElementById('set-copilotReasoningSummaries')?.checked,
849
885
  maxBudgetUsd: (document.getElementById('set-maxBudgetUsd')?.value ?? '').trim(),
850
886
  disableModelDiscovery: !!document.getElementById('set-disableModelDiscovery')?.checked,
887
+ // W-mpmwxkrw000872ec — global font-size scale. Allowlist validation
888
+ // (and clamp messaging) lives server-side in handleSettingsUpdate.
889
+ fontSize: document.getElementById('set-fontSize')?.value || 'small',
851
890
  maxTurnsByType: (function() {
852
891
  var mbt = {};
853
892
  var types = ['explore', 'ask', 'review', 'implement', 'fix', 'test', 'verify', 'plan', 'decompose'];
@@ -912,6 +951,10 @@ async function saveSettings() {
912
951
  status.style.color = 'var(--green)';
913
952
  showToast('cmd-toast', 'Settings saved', true);
914
953
  }
954
+ // W-mpmwxkrw000872ec — apply font-size immediately + persist to
955
+ // localStorage so a hard reload still reflects the new pick (server is
956
+ // the cold-load authority; localStorage is the no-flash fast path).
957
+ try { applyFontSizePreference(enginePayload.fontSize); } catch (err) { /* tolerated — change shows on next reload */ }
915
958
  if (saveBtn) { saveBtn.innerHTML = '<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><path d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"/></svg> Saved'; saveBtn.style.color = 'var(--green)'; saveBtn.style.borderColor = 'var(--green)'; }
916
959
  setTimeout(function() {
917
960
  if (saveBtn) { saveBtn.disabled = false; saveBtn.style.opacity = ''; saveBtn.innerHTML = '<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><path d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"/></svg> Save'; }
@@ -5,6 +5,19 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Minions Mission Control{{title_suffix}}</title>
7
7
  <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>{{favicon_emoji}}</text></svg>">
8
+ <script>
9
+ // W-mpmwxkrw000872ec — apply the saved font-size BEFORE the stylesheet
10
+ // parses so the first paint already reflects the user's preference (no
11
+ // flash of unstyled font size). localStorage is the fast path; the server
12
+ // value is reconciled later in refresh.js's first tick.
13
+ (function() {
14
+ try {
15
+ var v = localStorage.getItem('minions:font-size');
16
+ var valid = { small: 1, medium: 1, large: 1, xlarge: 1 };
17
+ document.documentElement.setAttribute('data-font-size', (v && valid[v]) ? v : 'small');
18
+ } catch (e) { /* private mode / disabled storage — fall through to default */ }
19
+ })();
20
+ </script>
8
21
  <style>/* __CSS__ */</style>
9
22
  </head>
10
23
  <body>
@@ -1,4 +1,12 @@
1
1
  :root {
2
+ /* W-mpmwxkrw000872ec — global dashboard font-size scale. Driven by
3
+ [data-font-size] on <html>. 'small' is the historic default (1.0) so
4
+ existing users see no change. Body-level CSS `zoom` is the cheapest
5
+ way to scale every px/em/rem rule across the SPA (typography tokens
6
+ below, slim.html, and the many inline `font-size:11px` styles) without
7
+ rewriting every rule. Scales modals, drawers, and fixed-position
8
+ elements because they all live inside <body>. */
9
+ --minions-font-scale: 1;
2
10
  --bg: #0d1117; --surface: #161b22; --surface2: #21262d; --border: #30363d;
3
11
  --text: #e6edf3; --muted: #8b949e; --green: #3fb950; --yellow: #d29922;
4
12
  --blue: #58a6ff; --purple: #bc8cff; --red: #f85149; --orange: #e3b341;
@@ -23,9 +31,17 @@
23
31
  /* Transitions */
24
32
  --transition-fast: 0.15s; --transition-base: 0.2s; --transition-slow: 0.3s;
25
33
  }
34
+ /* W-mpmwxkrw000872ec — font-size scale overrides. Values picked so the
35
+ largest tier (~1.30) tops out near 18px effective for the default 14px
36
+ body without overflowing chips/badges/tables at /, /pull-requests, /qa,
37
+ /settings (spot-checked). */
38
+ :root[data-font-size="small"] { --minions-font-scale: 1; }
39
+ :root[data-font-size="medium"] { --minions-font-scale: 1.08; }
40
+ :root[data-font-size="large"] { --minions-font-scale: 1.18; }
41
+ :root[data-font-size="xlarge"] { --minions-font-scale: 1.30; }
26
42
  * { box-sizing: border-box; margin: 0; padding: 0; }
27
43
  html, body { height: 100%; margin: 0; overflow: hidden; }
28
- body { background: var(--bg); color: var(--text); font-family: 'Segoe UI', system-ui, sans-serif; font-size: var(--text-xl); display: flex; flex-direction: column; }
44
+ body { background: var(--bg); color: var(--text); font-family: 'Segoe UI', system-ui, sans-serif; font-size: var(--text-xl); display: flex; flex-direction: column; zoom: var(--minions-font-scale, 1); }
29
45
 
30
46
  header {
31
47
  background: var(--surface); border-bottom: 1px solid var(--border);
@@ -730,8 +746,6 @@
730
746
  .settings-rail-btn { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: var(--space-3) var(--space-6); background: transparent; border: none; border-left: 3px solid transparent; color: var(--muted); font-size: var(--text-md); font-weight: 400; font-family: inherit; cursor: pointer; text-align: left; transition: all var(--transition-fast); }
731
747
  .settings-rail-btn:hover { color: var(--text); background: var(--surface); }
732
748
  .settings-rail-btn.active { color: var(--blue); border-left-color: var(--blue); background: var(--surface); font-weight: 600; }
733
- .settings-rail-btn.featured { color: var(--text); }
734
- .settings-rail-btn.featured.active { color: var(--blue); }
735
749
  .settings-rail-btn.no-match { opacity: 0.3; }
736
750
  .settings-rail-btn .match-count { font-size: var(--text-xs); color: var(--blue); background: var(--bg); border-radius: var(--radius-xl); padding: 1px 6px; min-width: 18px; text-align: center; }
737
751
  .settings-content { flex: 1; overflow-y: auto; padding: var(--space-7) var(--space-8); min-width: 0; }
package/dashboard.js CHANGED
@@ -9100,6 +9100,15 @@ What would you like to discuss or change? When you're happy, say "approve" and I
9100
9100
  else if (valid.includes(e.copilotStreamMode)) _setEngineConfig('copilotStreamMode', e.copilotStreamMode);
9101
9101
  else _clamped.push(`copilotStreamMode: "${e.copilotStreamMode}" not in [on, off] (kept previous value)`);
9102
9102
  }
9103
+ // W-mpmwxkrw000872ec — fontSize allowlist. Clamps invalid values
9104
+ // (rather than silently failing) so the dashboard bootstrap never
9105
+ // ends up with an unknown data-font-size attribute.
9106
+ if (e.fontSize !== undefined) {
9107
+ const valid = ['small', 'medium', 'large', 'xlarge'];
9108
+ if (_isClear(e.fontSize)) _deleteEngineConfig('fontSize');
9109
+ else if (valid.includes(e.fontSize)) _setEngineConfig('fontSize', e.fontSize);
9110
+ else _clamped.push(`fontSize: "${e.fontSize}" not in [${valid.join(', ')}] (kept previous value)`);
9111
+ }
9103
9112
  // maxBudgetUsd uses ?? semantics — 0 is a valid cap (read-only / dry-run agents).
9104
9113
  if (e.maxBudgetUsd !== undefined) {
9105
9114
  if (_isClear(e.maxBudgetUsd)) _deleteEngineConfig('maxBudgetUsd');
package/engine/shared.js CHANGED
@@ -1878,6 +1878,13 @@ const ENGINE_DEFAULTS = {
1878
1878
  ccUseWorkerPool: false, // Sub-task C of W-mp2w003600196c51 (CC perf): when true AND CC runtime is copilot, _invokeCcStream routes through engine/cc-worker-pool.js (persistent `copilot --acp` per CC tab) instead of spawning a fresh CLI per turn. Off by default — opt-in feature flag. **Structurally copilot-only**: the pool spawns `copilot --acp` (Agent Client Protocol); Claude Code does not implement ACP, so resolveCcUseWorkerPool returns false on non-copilot CC runtimes even with explicit-true (W-mphlriic00095f69 — prevents silent runtime switch). Engine/agent dispatch path stays per-process regardless.
1879
1879
  maxBudgetUsd: undefined, // fleet USD ceiling for --max-budget-usd (per-agent override: agents.<id>.maxBudgetUsd). Honors 0 via ?? so a literal cap of $0 works
1880
1880
  disableModelDiscovery: false, // skip runtime.listModels() REST calls fleet-wide (settings UI falls back to free-text)
1881
+ // W-mpmwxkrw000872ec — dashboard global font-size scale. Drives the
1882
+ // [data-font-size] attribute on <html> via the inline bootstrap script in
1883
+ // layout.html (localStorage fast path) and is reconciled from the server
1884
+ // value on cold loads. Valid: 'small' (current default), 'medium', 'large',
1885
+ // 'xlarge'. Allowlist validation lives in handleSettingsUpdate so invalid
1886
+ // values are clamped rather than silently breaking the bootstrap.
1887
+ fontSize: 'small',
1881
1888
  maxPendingContexts: 20, // cap pendingContexts arrays in cooldowns.json to prevent unbounded growth
1882
1889
  maxPendingContextEntryBytes: 256 * 1024, // 256 KB — cap each pendingContexts entry to prevent huge PR comments from bloating cooldowns.json
1883
1890
  maxDispatchPromptBytes: 1024 * 1024, // 1 MB — dispatch items with prompts larger than this sidecar to engine/contexts/ to prevent dispatch.json OOM (#1167)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2055",
3
+ "version": "0.1.2056",
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"