@yemi33/minions 0.1.2180 → 0.1.2181

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.
@@ -1167,21 +1167,16 @@ document.addEventListener('visibilitychange', function() {
1167
1167
  // Ring buffer capturing the last 50 /api/status poll cycles so a user
1168
1168
  // reporting "the dashboard didn't auto-update when X changed" can paste
1169
1169
  // `window._refreshDiagnostics()` from devtools or click the "diag" footer
1170
- // chip to surface a table. Gated behind feature flag
1171
- // `dashboard-refresh-diagnostics` (engine/features.js) disabled in
1172
- // production, enabled in dev. When disabled, the helpers below are no-ops
1173
- // (the boolean check is the first line of every collection path) so the
1174
- // steady-state poll loop is byte-identical to its pre-flag behaviour.
1170
+ // chip to surface a table. The capture is cheap (in-memory ring buffer,
1171
+ // no network, no DOM writes) and always on (P-c1d4e8b6 deleted the
1172
+ // per-flag gate that had shipped default ON since v0.1.2034) so the next
1173
+ // staleness complaint can be diagnosed immediately from
1174
+ // window._refreshDiagnostics without a settings flip.
1175
1175
  const _DIAG_RING_SIZE = 50;
1176
1176
  const _refreshDiagBuf = [];
1177
1177
  let _prevRefreshTs = 0;
1178
1178
  let _diagHiddenSinceMs = 0; // ts when tab last went hidden; 0 when visible
1179
1179
  let _lastVisibilityChangeAt = 0;
1180
- function _isRefreshDiagOn() {
1181
- try {
1182
- return !!(window.MinionsFeatures && window.MinionsFeatures.isOn('dashboard-refresh-diagnostics'));
1183
- } catch { return false; }
1184
- }
1185
1180
  function _pushDiagEntry(entry) {
1186
1181
  _refreshDiagBuf.push(entry);
1187
1182
  if (_refreshDiagBuf.length > _DIAG_RING_SIZE) _refreshDiagBuf.shift();
@@ -1191,7 +1186,6 @@ function _pushDiagEntry(entry) {
1191
1186
  window._refreshDiagnostics = function() { return _refreshDiagBuf.slice(); };
1192
1187
  window._refreshDiagnosticsClear = function() { _refreshDiagBuf.length = 0; };
1193
1188
  document.addEventListener('visibilitychange', function() {
1194
- if (!_isRefreshDiagOn()) return;
1195
1189
  const now = Date.now();
1196
1190
  const vs = document.visibilityState;
1197
1191
  let hiddenForMs = null;
@@ -1220,11 +1214,10 @@ async function refresh(opts) {
1220
1214
  // console-spam and adds load to whatever's wedged).
1221
1215
  if (_nextPollAllowedAt && Date.now() < _nextPollAllowedAt) return;
1222
1216
  _refreshInFlight = true;
1223
- const _diagOn = _isRefreshDiagOn();
1224
- const _t0 = _diagOn ? Date.now() : 0;
1225
- const _gap = _diagOn && _prevRefreshTs ? _t0 - _prevRefreshTs : null;
1226
- if (_diagOn) _prevRefreshTs = _t0;
1227
- const _diagEntry = _diagOn ? {
1217
+ const _t0 = Date.now();
1218
+ const _gap = _prevRefreshTs ? _t0 - _prevRefreshTs : null;
1219
+ _prevRefreshTs = _t0;
1220
+ const _diagEntry = {
1228
1221
  ts: _t0,
1229
1222
  kind: 'refresh',
1230
1223
  gap_since_prev_ms: _gap,
@@ -1239,7 +1232,7 @@ async function refresh(opts) {
1239
1232
  changed: null,
1240
1233
  render_duration_ms: null,
1241
1234
  error_message: null,
1242
- } : null;
1235
+ };
1243
1236
  try {
1244
1237
  const headers = {};
1245
1238
  if (_lastStatusEtag) headers['If-None-Match'] = _lastStatusEtag;
@@ -1253,16 +1246,14 @@ async function refresh(opts) {
1253
1246
  if (res.status === 304 && _lastStatusData) {
1254
1247
  // Cache hit — reuse last payload, skip parsing entirely.
1255
1248
  data = _lastStatusData;
1256
- if (_diagEntry) {
1257
- _diagEntry.response_status = '304';
1258
- _diagEntry.bytes_received = 0;
1259
- // D2: capture etag on 304 so the diag table can show whether the
1260
- // server's ETag advanced even when we're reusing the cached body.
1261
- // Without this the "etag↓" column is blank on every 304 row and the
1262
- // operator can't tell server-side advancement from a pinned cache.
1263
- const etag304 = res.headers && (res.headers.get ? res.headers.get('etag') : null);
1264
- if (etag304) _diagEntry.etag_received = etag304;
1265
- }
1249
+ _diagEntry.response_status = '304';
1250
+ _diagEntry.bytes_received = 0;
1251
+ // D2: capture etag on 304 so the diag table can show whether the
1252
+ // server's ETag advanced even when we're reusing the cached body.
1253
+ // Without this the "etag↓" column is blank on every 304 row and the
1254
+ // operator can't tell server-side advancement from a pinned cache.
1255
+ const etag304 = res.headers && (res.headers.get ? res.headers.get('etag') : null);
1256
+ if (etag304) _diagEntry.etag_received = etag304;
1266
1257
  } else {
1267
1258
  data = await res.json();
1268
1259
  const etag = res.headers && (res.headers.get ? res.headers.get('etag') : null);
@@ -1270,12 +1261,10 @@ async function refresh(opts) {
1270
1261
  _lastStatusEtag = etag;
1271
1262
  _lastStatusData = data;
1272
1263
  }
1273
- if (_diagEntry) {
1274
- _diagEntry.response_status = String(res.status);
1275
- _diagEntry.etag_received = etag || null;
1276
- const cl = res.headers && (res.headers.get ? res.headers.get('content-length') : null);
1277
- _diagEntry.bytes_received = cl != null && cl !== '' ? Number(cl) : null;
1278
- }
1264
+ _diagEntry.response_status = String(res.status);
1265
+ _diagEntry.etag_received = etag || null;
1266
+ const cl = res.headers && (res.headers.get ? res.headers.get('content-length') : null);
1267
+ _diagEntry.bytes_received = cl != null && cl !== '' ? Number(cl) : null;
1279
1268
  }
1280
1269
  // Auto-reload policy (2026-05-29):
1281
1270
  // - dashboardStartedAt change → reload (covers `minions restart`)
@@ -1302,31 +1291,26 @@ async function refresh(opts) {
1302
1291
  _consecutiveStatusFails = 0;
1303
1292
  _nextPollAllowedAt = 0;
1304
1293
  if (_unreachableSince) _markDashboardReachable();
1305
- const _renderStart = _diagOn ? Date.now() : 0;
1306
- let _diagChanges = null;
1307
- if (_diagOn) {
1308
- _diagChanges = {};
1309
- _lastChangedFlags = _diagChanges;
1310
- }
1294
+ const _renderStart = Date.now();
1295
+ const _diagChanges = {};
1296
+ _lastChangedFlags = _diagChanges;
1311
1297
  try {
1312
1298
  _processStatusUpdate(data, opts);
1313
1299
  } finally {
1314
- if (_diagOn) {
1315
- _lastChangedFlags = null;
1316
- _diagEntry.render_duration_ms = Date.now() - _renderStart;
1317
- _diagEntry.changed = _diagChanges;
1318
- _diagEntry.workItems_changed = !!(_diagChanges && _diagChanges.workItems);
1319
- // Post-slim, data.workItems is undefined; read from the dedicated
1320
- // endpoint cache.
1321
- _diagEntry.workItems_count = (window._lastWorkItems || []).length;
1322
- _diagEntry.statusCacheVersion = (data.version && data.version.statusCacheVersion) != null
1323
- ? data.version.statusCacheVersion
1324
- : null;
1325
- }
1300
+ _lastChangedFlags = null;
1301
+ _diagEntry.render_duration_ms = Date.now() - _renderStart;
1302
+ _diagEntry.changed = _diagChanges;
1303
+ _diagEntry.workItems_changed = !!(_diagChanges && _diagChanges.workItems);
1304
+ // Post-slim, data.workItems is undefined; read from the dedicated
1305
+ // endpoint cache.
1306
+ _diagEntry.workItems_count = (window._lastWorkItems || []).length;
1307
+ _diagEntry.statusCacheVersion = (data.version && data.version.statusCacheVersion) != null
1308
+ ? data.version.statusCacheVersion
1309
+ : null;
1326
1310
  }
1327
1311
  } catch(e) {
1328
1312
  console.error('refresh error', e);
1329
- if (_diagEntry && !_diagEntry.response_status) {
1313
+ if (!_diagEntry.response_status) {
1330
1314
  _diagEntry.response_status = (e && e.name === 'AbortError') ? 'abort' : 'error';
1331
1315
  _diagEntry.error_message = String((e && e.message) || e);
1332
1316
  }
@@ -1346,7 +1330,7 @@ async function refresh(opts) {
1346
1330
  }
1347
1331
  finally {
1348
1332
  _refreshInFlight = false;
1349
- if (_diagEntry) _pushDiagEntry(_diagEntry);
1333
+ _pushDiagEntry(_diagEntry);
1350
1334
  }
1351
1335
  }
1352
1336
 
@@ -1394,15 +1378,11 @@ switchPage(currentPage);
1394
1378
  window.MinionsRefresh = { refresh };
1395
1379
 
1396
1380
  // ── Refresh-diagnostic footer chip + modal (W-mphejzx100081972) ───────────
1397
- // Injected only when the dashboard-refresh-diagnostics feature flag is on
1398
- // (engine/features.js → MINIONS_FEATURES.flags). The chip floats bottom-right
1399
- // and opens a simple table view of the ring buffer plus a "send to engine"
1400
- // button that POSTs the snapshot to /api/diagnostics/refresh. No DOM is
1401
- // touched (and no listeners installed beyond the visibilitychange handler
1402
- // above, which itself short-circuits when the flag is off) when the flag is
1403
- // disabled — production behaviour stays untouched.
1381
+ // Always installed (P-c1d4e8b6 deleted the per-flag gate that previously
1382
+ // guarded this). The chip floats bottom-left and opens a simple table view
1383
+ // of the ring buffer plus a "send to engine" button that POSTs the
1384
+ // snapshot to /api/diagnostics/refresh.
1404
1385
  (function installRefreshDiagChip() {
1405
- if (!_isRefreshDiagOn()) return;
1406
1386
  try {
1407
1387
  const chip = document.createElement('button');
1408
1388
  chip.id = 'refresh-diag-chip';
@@ -74,19 +74,51 @@ const SLIM_JS_ORDER = [
74
74
  'command-send', 'status', 'members', 'modals-tiles', 'history', 'pinned',
75
75
  ];
76
76
 
77
+ // Cache for the assembled slim source fragments (layout/css/body/js). Keyed on
78
+ // the input file mtimes so dev edits still take effect on the next request
79
+ // without a server restart, while production serves repeated requests without
80
+ // re-reading the ~14 fragment files each time. Phase 3 perf audit (P-a7b8c9d0)
81
+ // measured `buildSlimHtml()` at ~7.7 ms/req pre-cache; post-cache it's ~14
82
+ // cheap fs.statSync() calls + one string substitution per request (~sub-ms).
83
+ let _slimPartsCache = null;
84
+
85
+ function _slimSourcePaths(slimDir) {
86
+ return [
87
+ path.join(slimDir, 'layout.html'),
88
+ path.join(slimDir, 'styles.css'),
89
+ path.join(slimDir, 'body.html'),
90
+ ...SLIM_JS_ORDER.map(f => path.join(slimDir, 'js', f + '.js')),
91
+ ];
92
+ }
93
+
94
+ function _statMtimeOrZero(p) {
95
+ try { return fs.statSync(p).mtimeMs; } catch { return 0; }
96
+ }
97
+
77
98
  function buildSlimHtml(opts) {
78
99
  const slimDir = path.join(MINIONS_DIR, 'dashboard', 'slim');
79
100
  const layoutPath = path.join(slimDir, 'layout.html');
80
101
  if (!fs.existsSync(layoutPath)) {
81
102
  throw new Error(`Slim layout not found: ${layoutPath}. The dashboard/slim/ directory must exist.`);
82
103
  }
83
- const layout = safeRead(layoutPath);
84
- const css = safeRead(path.join(slimDir, 'styles.css'));
85
- const body = safeRead(path.join(slimDir, 'body.html'));
86
- // Join with a newline so a fragment that loses its trailing newline can't
87
- // glue two statements together (e.g. `}var foo`). Parts share one IIFE
88
- // scope, so order is load-bearing — see SLIM_JS_ORDER.
89
- const js = SLIM_JS_ORDER.map(f => safeRead(path.join(slimDir, 'js', f + '.js'))).join('\n');
104
+ const paths = _slimSourcePaths(slimDir);
105
+ const mtimes = paths.map(_statMtimeOrZero);
106
+ if (!_slimPartsCache
107
+ || _slimPartsCache.mtimes.length !== mtimes.length
108
+ || _slimPartsCache.mtimes.some((m, i) => m !== mtimes[i])) {
109
+ const [lp, cp, bp, ...jsPaths] = paths;
110
+ _slimPartsCache = {
111
+ mtimes,
112
+ layout: safeRead(lp),
113
+ css: safeRead(cp),
114
+ body: safeRead(bp),
115
+ // Join with a newline so a fragment that loses its trailing newline
116
+ // can't glue two statements together (e.g. `}var foo`). Parts share one
117
+ // IIFE scope, so order is load-bearing — see SLIM_JS_ORDER.
118
+ js: jsPaths.map(p => safeRead(p)).join('\n'),
119
+ };
120
+ }
121
+ const { layout, css, body, js } = _slimPartsCache;
90
122
 
91
123
  // Feature-flag bootstrap (window.MINIONS_FEATURES) injected at the top of the
92
124
  // slim IIFE. serveSlimUx passes the live flags as JSON via opts.featuresJson;
@@ -103,4 +135,8 @@ function buildSlimHtml(opts) {
103
135
  .replace('/* __JS__ */', () => js);
104
136
  }
105
137
 
106
- module.exports = { buildDashboardHtml, buildSlimHtml, SLIM_JS_ORDER };
138
+ // exported for testing lets tests reset the module-scope cache so they can
139
+ // observe a cold first call without depending on test execution order.
140
+ function _resetSlimPartsCacheForTest() { _slimPartsCache = null; }
141
+
142
+ module.exports = { buildDashboardHtml, buildSlimHtml, SLIM_JS_ORDER, _resetSlimPartsCacheForTest };
@@ -78,24 +78,6 @@ const FEATURES = {
78
78
  addedIn: '0.1.1916',
79
79
  requiredCcRuntime: 'copilot',
80
80
  },
81
- // dashboard-refresh-diagnostics — W-mphejzx100081972. Enables the in-browser
82
- // /api/status poll telemetry ring buffer in dashboard/js/refresh.js: per-cycle
83
- // ETag/status/bytes/render-duration capture, visibility-transition tracking,
84
- // a small "diag" footer chip that opens a table modal, and a "send to engine"
85
- // button that POSTs the buffer to /api/diagnostics/refresh for offline triage.
86
- // Default ON (W-mphlr4lv0008c24f) — the diagnostic capture is cheap (in-memory
87
- // ring buffer, no network, no DOM writes) and is gated by _isRefreshDiagOn()
88
- // at the top of refresh() so the disabled path remains byte-identical to the
89
- // pre-flag steady state. Having it always-on means the next staleness
90
- // complaint can be diagnosed immediately from window._refreshDiagnostics
91
- // without needing a settings flip first.
92
- // Disable via config.features['dashboard-refresh-diagnostics']: false or
93
- // env MINIONS_FEATURE_DASHBOARD_REFRESH_DIAGNOSTICS=0.
94
- 'dashboard-refresh-diagnostics': {
95
- description: 'Capture the last 50 dashboard /api/status poll cycles (timing, ETag, status code, render duration, per-renderer change flags) in a browser-side ring buffer accessible via window._refreshDiagnostics, a footer "diag" chip, and POST /api/diagnostics/refresh.',
96
- default: true,
97
- addedIn: '0.1.2034',
98
- },
99
81
  };
100
82
 
101
83
  const ENV_TRUTHY = new Set(['1', 'true', 'on', 'yes']);
package/engine/queries.js CHANGED
@@ -1516,6 +1516,14 @@ function getKnowledgeBaseEntriesSnapshot() {
1516
1516
  }
1517
1517
 
1518
1518
  async function _scanKnowledgeBase() {
1519
+ // V8 sliced-string + regex-capture footgun (P-b8c9d0e1, Phase 2 audit):
1520
+ // `String.prototype.slice` and `match()` capture groups both produce
1521
+ // sliced strings that retain the entire `content` (100-400 KB per file)
1522
+ // alive in heap as long as any short derivative is referenced. The
1523
+ // Phase 2 heap snapshot measured ~6.5 MB retained per process across
1524
+ // ~30 large KB entries. `_flat()` materialises a fresh flat string via
1525
+ // Buffer round-trip so the cached entry no longer pins the parent file.
1526
+ const _flat = (s) => Buffer.from(String(s || ''), 'utf8').toString('utf8');
1519
1527
  const entries = [];
1520
1528
  for (const cat of KB_CATEGORIES) {
1521
1529
  const catDir = path.join(KNOWLEDGE_DIR, cat);
@@ -1527,19 +1535,19 @@ async function _scanKnowledgeBase() {
1527
1535
  fsp.stat(filePath).catch(() => null),
1528
1536
  ]);
1529
1537
  const titleMatch = content.match(/^#\s+(.+)/m);
1530
- const title = titleMatch ? titleMatch[1].trim() : f.replace(/\.md$/, '');
1538
+ const title = _flat(titleMatch ? titleMatch[1].trim() : f.replace(/\.md$/, ''));
1531
1539
  const agentMatch = f.match(/^\d{4}-\d{2}-\d{2}-(\w+)-/);
1532
1540
  const dateMatch = f.match(/^(\d{4}-\d{2}-\d{2})/) || content.match(/^date:\s*(\d{4}-\d{2}-\d{2})$/m);
1533
1541
  const sourceMatch = content.match(/^source:\s*(.+)/m);
1534
1542
  const sortTs = (stat && stat.mtimeMs) || 0;
1535
- const displayDate = dateMatch ? dateMatch[1] : (sortTs ? new Date(sortTs).toISOString().slice(0, 10) : '');
1543
+ const displayDate = dateMatch ? _flat(dateMatch[1]) : (sortTs ? new Date(sortTs).toISOString().slice(0, 10) : '');
1536
1544
  return {
1537
1545
  cat, file: f, title,
1538
1546
  agent: agentMatch ? agentMatch[1] : '',
1539
1547
  date: displayDate,
1540
1548
  sortTs,
1541
- source: sourceMatch ? sourceMatch[1].trim() : '',
1542
- preview: content.slice(0, 200),
1549
+ source: _flat(sourceMatch ? sourceMatch[1].trim() : ''),
1550
+ preview: _flat(content.slice(0, 200)),
1543
1551
  size: content.length,
1544
1552
  };
1545
1553
  }));
package/engine/shared.js CHANGED
@@ -3018,10 +3018,6 @@ function resolvePollFlag(engineCfg, granularKey, legacyMacroKey) {
3018
3018
  // (CLI chooses)" option (which submits an empty string) clears the override
3019
3019
  // instead of pinning the runtime to nothing.
3020
3020
 
3021
- function _isMeaningful(v) {
3022
- return v !== undefined && v !== null && v !== '';
3023
- }
3024
-
3025
3021
  /**
3026
3022
  * Resolve the CLI runtime for a per-agent spawn. Priority:
3027
3023
  * 1. `agent.cli` — per-agent override
@@ -3031,8 +3027,8 @@ function _isMeaningful(v) {
3031
3027
  * Does NOT fall through to `engine.ccCli`. CC and agents are independent paths.
3032
3028
  */
3033
3029
  function resolveAgentCli(agent, engine) {
3034
- if (agent && _isMeaningful(agent.cli)) return String(agent.cli);
3035
- if (engine && _isMeaningful(engine.defaultCli)) return String(engine.defaultCli);
3030
+ if (agent && agent.cli !== undefined && agent.cli !== null && agent.cli !== '') return String(agent.cli);
3031
+ if (engine && engine.defaultCli !== undefined && engine.defaultCli !== null && engine.defaultCli !== '') return String(engine.defaultCli);
3036
3032
  return ENGINE_DEFAULTS.defaultCli;
3037
3033
  }
3038
3034
 
@@ -3046,8 +3042,8 @@ function resolveAgentCli(agent, engine) {
3046
3042
  * it's a fleet-wide singleton.
3047
3043
  */
3048
3044
  function resolveCcCli(engine) {
3049
- if (engine && _isMeaningful(engine.ccCli)) return String(engine.ccCli);
3050
- if (engine && _isMeaningful(engine.defaultCli)) return String(engine.defaultCli);
3045
+ if (engine && engine.ccCli !== undefined && engine.ccCli !== null && engine.ccCli !== '') return String(engine.ccCli);
3046
+ if (engine && engine.defaultCli !== undefined && engine.defaultCli !== null && engine.defaultCli !== '') return String(engine.defaultCli);
3051
3047
  return ENGINE_DEFAULTS.defaultCli;
3052
3048
  }
3053
3049
 
@@ -3068,7 +3064,8 @@ function resolveCcCli(engine) {
3068
3064
  * 4. ENGINE_DEFAULTS.ccUseWorkerPool — final fallback
3069
3065
  *
3070
3066
  * Strict boolean check on the override so a literal `false` opts out even on
3071
- * Copilot, matching `_isMeaningful` semantics for boolean flags.
3067
+ * Copilot, matching the "treat empty/null/undefined as unset" semantics used
3068
+ * throughout the resolve* helpers for boolean flags.
3072
3069
  */
3073
3070
  function resolveCcUseWorkerPool(engine) {
3074
3071
  // Guard 1 (W-mphlriic00095f69): pool transport is ACP-only. If CC runtime
@@ -3093,8 +3090,8 @@ function resolveCcUseWorkerPool(engine) {
3093
3090
  * to the user's `~/.copilot/settings.json` model).
3094
3091
  */
3095
3092
  function resolveAgentModel(agent, engine) {
3096
- if (agent && _isMeaningful(agent.model)) return String(agent.model);
3097
- if (engine && _isMeaningful(engine.defaultModel)) return String(engine.defaultModel);
3093
+ if (agent && agent.model !== undefined && agent.model !== null && agent.model !== '') return String(agent.model);
3094
+ if (engine && engine.defaultModel !== undefined && engine.defaultModel !== null && engine.defaultModel !== '') return String(engine.defaultModel);
3098
3095
  return undefined;
3099
3096
  }
3100
3097
 
@@ -3105,8 +3102,8 @@ function resolveAgentModel(agent, engine) {
3105
3102
  * 3. `undefined` — let the runtime adapter pick
3106
3103
  */
3107
3104
  function resolveCcModel(engine) {
3108
- if (engine && _isMeaningful(engine.ccModel)) return String(engine.ccModel);
3109
- if (engine && _isMeaningful(engine.defaultModel)) return String(engine.defaultModel);
3105
+ if (engine && engine.ccModel !== undefined && engine.ccModel !== null && engine.ccModel !== '') return String(engine.ccModel);
3106
+ if (engine && engine.defaultModel !== undefined && engine.defaultModel !== null && engine.defaultModel !== '') return String(engine.defaultModel);
3110
3107
  return undefined;
3111
3108
  }
3112
3109
 
@@ -3233,14 +3230,12 @@ let _legacyCcModelMigrationLogged = false;
3233
3230
  function applyLegacyCcModelMigration(config, { logger = log } = {}) {
3234
3231
  if (!config || !config.engine || typeof config.engine !== 'object') return false;
3235
3232
  const e = config.engine;
3236
- if (_isMeaningful(e.defaultModel)) return false;
3237
- if (!_isMeaningful(e.ccModel)) return false;
3233
+ if (e.defaultModel !== undefined && e.defaultModel !== null && e.defaultModel !== '') return false;
3234
+ if (!(e.ccModel !== undefined && e.ccModel !== null && e.ccModel !== '')) return false;
3238
3235
  e.defaultModel = e.ccModel;
3239
3236
  if (!_legacyCcModelMigrationLogged) {
3240
3237
  _legacyCcModelMigrationLogged = true;
3241
- try {
3242
- logger('warn', 'ccModel is now a CC-specific override; set defaultModel to apply fleet-wide');
3243
- } catch { /* logger may not be wired during tests — best-effort */ }
3238
+ logger('warn', 'ccModel is now a CC-specific override; set defaultModel to apply fleet-wide');
3244
3239
  }
3245
3240
  return true;
3246
3241
  }
@@ -3332,7 +3327,7 @@ function runtimeConfigWarnings(config, registeredRuntimes) {
3332
3327
  // 1. Unknown CLI values across the fleet.
3333
3328
  const seen = new Set();
3334
3329
  const checkCli = (label, value) => {
3335
- if (!_isMeaningful(value)) return;
3330
+ if (!(value !== undefined && value !== null && value !== '')) return;
3336
3331
  const key = `${label}:${value}`;
3337
3332
  if (seen.has(key)) return;
3338
3333
  seen.add(key);
@@ -3376,7 +3371,7 @@ function runtimeConfigWarnings(config, registeredRuntimes) {
3376
3371
  const ccCli = resolveCcCli(engine);
3377
3372
  let ccRuntime = null;
3378
3373
  try { ccRuntime = require('./runtimes').resolveRuntime(ccCli); } catch { /* unknown runtime — skip */ }
3379
- if (ccRuntime?.capabilities?.bareMode === true && !_isMeaningful(engine.ccSystemPrompt)) {
3374
+ if (ccRuntime?.capabilities?.bareMode === true && !(engine.ccSystemPrompt !== undefined && engine.ccSystemPrompt !== null && engine.ccSystemPrompt !== '')) {
3380
3375
  warnings.push({
3381
3376
  id: 'bare-mode-misconfig',
3382
3377
  message: `engine.claudeBareMode is true but CC runs on ${ccCli} (which honours --bare) with no engine.ccSystemPrompt — CLAUDE.md auto-discovery is suppressed and CC will lose project context.`,
@@ -5102,11 +5097,7 @@ function sanitizeBranch(name) {
5102
5097
  // of side-effecting child_process imports at module load.
5103
5098
 
5104
5099
  function getOperatorLogin(config) {
5105
- try {
5106
- return require('./operator-identity').resolveOperatorLogin(config || {});
5107
- } catch {
5108
- return null;
5109
- }
5100
+ return require('./operator-identity').resolveOperatorLogin(config || {});
5110
5101
  }
5111
5102
 
5112
5103
  function deriveWorkItemBranchName(item, config) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2180",
3
+ "version": "0.1.2181",
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"