@yemi33/minions 0.1.1599 → 0.1.1601
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/CHANGELOG.md +5 -1
- package/dashboard/js/command-center.js +17 -1
- package/dashboard/js/refresh.js +18 -0
- package/dashboard/js/settings.js +66 -1
- package/dashboard/layout.html +1 -1
- package/dashboard/pages/home.html +1 -1
- package/dashboard.js +101 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.1601 (2026-04-28)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
6
|
- match runtime tags to actual logos (pixel-crab Claude, mascot Copilot)
|
|
7
7
|
- replace runtime text tag with inline SVG logos
|
|
8
8
|
|
|
9
9
|
### Fixes
|
|
10
|
+
- runtime-aware model picker + cross-runtime validation
|
|
10
11
|
- keep cc stream final text complete
|
|
11
12
|
- switch Copilot icon to outline style for cleaner inline read
|
|
12
13
|
- un-invert Copilot — purple silhouette + white cutouts
|
|
13
14
|
- redraw Copilot icon to match actual mascot — vertical eye pills, not grill bars
|
|
14
15
|
- invert Copilot icon colors for better dark-theme visibility
|
|
15
16
|
|
|
17
|
+
### Other
|
|
18
|
+
- Stream CC chunks incrementally
|
|
19
|
+
|
|
16
20
|
## 0.1.1592 (2026-04-28)
|
|
17
21
|
|
|
18
22
|
### Features
|
|
@@ -54,6 +54,22 @@ function _ccActiveTab() {
|
|
|
54
54
|
return _ccTabs.find(function(t) { return t.id === _ccActiveTabId; }) || null;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
function _ccMergeStreamText(prev, incoming) {
|
|
58
|
+
var current = prev || '';
|
|
59
|
+
var next = incoming || '';
|
|
60
|
+
if (!current) return next;
|
|
61
|
+
if (!next) return current;
|
|
62
|
+
if (next === current) return current;
|
|
63
|
+
if (next.startsWith(current)) return next;
|
|
64
|
+
if (current.startsWith(next)) return current;
|
|
65
|
+
for (var overlap = Math.min(current.length, next.length); overlap > 0; overlap--) {
|
|
66
|
+
if (current.slice(-overlap) === next.slice(0, overlap)) {
|
|
67
|
+
return current + next.slice(overlap);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return current + '\n\n' + next;
|
|
71
|
+
}
|
|
72
|
+
|
|
57
73
|
async function _ccDashboardHealth() {
|
|
58
74
|
var controller = new AbortController();
|
|
59
75
|
var timer = setTimeout(function() { controller.abort(); }, 3000);
|
|
@@ -580,7 +596,7 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
|
|
|
580
596
|
|
|
581
597
|
async function _handleEvent(evt) {
|
|
582
598
|
if (evt.type === 'chunk') {
|
|
583
|
-
streamedText = evt.text;
|
|
599
|
+
streamedText = _ccMergeStreamText(streamedText, evt.text || '');
|
|
584
600
|
if (activeTab) activeTab._streamedText = streamedText;
|
|
585
601
|
updateStreamDiv();
|
|
586
602
|
} else if (evt.type === 'heartbeat') {
|
package/dashboard/js/refresh.js
CHANGED
|
@@ -35,6 +35,20 @@ function _changed(key, value) {
|
|
|
35
35
|
return true;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
function _formatCcPowerLabel(autoMode) {
|
|
39
|
+
var runtime = autoMode && autoMode.ccCli ? String(autoMode.ccCli) : 'claude';
|
|
40
|
+
var runtimeLabel = runtime.charAt(0).toUpperCase() + runtime.slice(1);
|
|
41
|
+
var model = autoMode && autoMode.ccModel ? String(autoMode.ccModel) : '';
|
|
42
|
+
return 'Ask anything, dispatch work, manage plans — powered by ' + runtimeLabel + (model ? ' (' + model + ')' : '');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function _formatCcDrawerLabel(autoMode) {
|
|
46
|
+
var runtime = autoMode && autoMode.ccCli ? String(autoMode.ccCli) : 'claude';
|
|
47
|
+
var runtimeLabel = runtime.charAt(0).toUpperCase() + runtime.slice(1);
|
|
48
|
+
var model = autoMode && autoMode.ccModel ? String(autoMode.ccModel) : '';
|
|
49
|
+
return runtimeLabel + (model ? ' (' + model + ')' : '') + '-powered. Full minions context. Enter to send, Shift+Enter for newline.';
|
|
50
|
+
}
|
|
51
|
+
|
|
38
52
|
function _processStatusUpdate(data) {
|
|
39
53
|
// Detect fresh install — clear stale browser state if install ID changed
|
|
40
54
|
if (data.installId) {
|
|
@@ -53,6 +67,10 @@ function _processStatusUpdate(data) {
|
|
|
53
67
|
if (autoEl) autoEl.innerHTML = data.autoMode?.approvePlans
|
|
54
68
|
? '<span style="font-size:9px;font-weight:600;padding:1px 6px;border-radius:3px;background:rgba(63,185,80,0.15);color:var(--green);border:1px solid rgba(63,185,80,0.3)">AUTO-APPROVE</span>'
|
|
55
69
|
: '';
|
|
70
|
+
const ccLabelEl = document.getElementById('cmd-powered-by');
|
|
71
|
+
if (ccLabelEl) ccLabelEl.textContent = _formatCcPowerLabel(data.autoMode);
|
|
72
|
+
const ccDrawerLabelEl = document.getElementById('cc-powered-by');
|
|
73
|
+
if (ccDrawerLabelEl) ccDrawerLabelEl.textContent = _formatCcDrawerLabel(data.autoMode);
|
|
56
74
|
const threshEl = document.getElementById('inbox-threshold');
|
|
57
75
|
if (threshEl && data.autoMode?.inboxThreshold) threshEl.textContent = data.autoMode.inboxThreshold;
|
|
58
76
|
|
package/dashboard/js/settings.js
CHANGED
|
@@ -35,7 +35,11 @@ async function openSettings() {
|
|
|
35
35
|
// <select> populated from /api/runtimes once the registry resolves.
|
|
36
36
|
'<input value="' + escHtml(a.cli || '') + '" placeholder="' + escHtml(fleetCliLabel) + ' (fleet)" disabled style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--muted);font-size:11px">' +
|
|
37
37
|
'</td>' +
|
|
38
|
-
'<td
|
|
38
|
+
'<td data-runtime-model="' + escHtml(id) + '" style="min-width:140px">' +
|
|
39
|
+
// Loading placeholder — initRuntimeFleetUI() replaces this with a
|
|
40
|
+
// <select> populated from /api/runtimes/<resolved-cli>/models.
|
|
41
|
+
'<input value="' + escHtml(a.model || '') + '" placeholder="' + escHtml(fleetModelLabel) + ' (fleet)" disabled style="width:120px;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--muted);font-size:11px">' +
|
|
42
|
+
'</td>' +
|
|
39
43
|
'<td><input data-agent="' + escHtml(id) + '" data-field="monthlyBudgetUsd" value="' + escHtml(a.monthlyBudgetUsd != null ? String(a.monthlyBudgetUsd) : '') + '" placeholder="unlimited" style="width:70px;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:11px;text-align:right"></td>' +
|
|
40
44
|
'</tr>';
|
|
41
45
|
}).join('');
|
|
@@ -347,6 +351,26 @@ async function initRuntimeFleetUI(engineCfg, agentsCfg) {
|
|
|
347
351
|
).join('') +
|
|
348
352
|
'</select>';
|
|
349
353
|
}
|
|
354
|
+
// Hydrate per-agent model dropdowns. The model list is keyed off the
|
|
355
|
+
// agent's RESOLVED runtime: per-agent override → fleet default. Without
|
|
356
|
+
// this the input was free-text and a user could (and did) save an agent
|
|
357
|
+
// with cli=claude + model=<some gpt> — invalid combination that crashed
|
|
358
|
+
// dispatch. Refreshing on CLI change clears stale model values.
|
|
359
|
+
const fleetDefaultCli = engineCfg.defaultCli || 'claude';
|
|
360
|
+
for (const cell of cliCells) {
|
|
361
|
+
const agentId = cell.getAttribute('data-runtime-cli');
|
|
362
|
+
const agent = (agentsCfg || {})[agentId] || {};
|
|
363
|
+
const resolvedCli = agent.cli || fleetDefaultCli;
|
|
364
|
+
loadModelsForAgent(agentId, resolvedCli, agent.model || '');
|
|
365
|
+
// CLI dropdown change → refresh that agent's model dropdown to match.
|
|
366
|
+
const sel = cell.querySelector('select[data-field="cli"]');
|
|
367
|
+
if (sel) {
|
|
368
|
+
sel.addEventListener('change', () => {
|
|
369
|
+
const newCli = sel.value || fleetDefaultCli;
|
|
370
|
+
loadModelsForAgent(agentId, newCli, ''); // clear value: previous model may not exist for the new runtime
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
350
374
|
|
|
351
375
|
// Models load for the resolved default + CC CLIs. ccCli falls back to
|
|
352
376
|
// defaultCli when unset — same rule as resolveCcCli().
|
|
@@ -409,6 +433,47 @@ async function loadModelsForRuntime(runtimeName, inputId, currentValue) {
|
|
|
409
433
|
wrap.innerHTML = '<select id="' + inputId + '" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px">' + opts + '</select>';
|
|
410
434
|
}
|
|
411
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Per-agent model hydrator. Replaces the placeholder input in the cell
|
|
438
|
+
* `[data-runtime-model="<agentId>"]` with a <select> of valid models for the
|
|
439
|
+
* given runtime. Output element keeps `data-agent` + `data-field="model"` so
|
|
440
|
+
* the existing save flow picks it up unchanged. Free-text input fallback
|
|
441
|
+
* when the runtime returns no model list (Claude / discovery disabled).
|
|
442
|
+
*/
|
|
443
|
+
async function loadModelsForAgent(agentId, runtimeName, currentValue) {
|
|
444
|
+
const cell = document.querySelector('[data-runtime-model="' + agentId + '"]');
|
|
445
|
+
if (!cell) return;
|
|
446
|
+
const baseAttrs = 'data-agent="' + escHtml(agentId) + '" data-field="model"';
|
|
447
|
+
const baseStyle = 'width:120px;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:11px';
|
|
448
|
+
if (!runtimeName) {
|
|
449
|
+
cell.innerHTML = '<input ' + baseAttrs + ' value="' + escHtml(currentValue || '') + '" placeholder="(no runtime)" disabled style="' + baseStyle + ';color:var(--muted)">';
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
let payload = { models: null };
|
|
453
|
+
try {
|
|
454
|
+
const res = await fetch('/api/runtimes/' + encodeURIComponent(runtimeName) + '/models');
|
|
455
|
+
if (res.ok) payload = await res.json();
|
|
456
|
+
} catch { /* fall through to free-text */ }
|
|
457
|
+
|
|
458
|
+
const models = Array.isArray(payload.models) ? payload.models : null;
|
|
459
|
+
if (!models || models.length === 0) {
|
|
460
|
+
cell.innerHTML = '<input ' + baseAttrs + ' value="' + escHtml(currentValue || '') + '" placeholder="' + escHtml(runtimeName) + ' default" style="' + baseStyle + '">';
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
let opts = '<option value=""' + (!currentValue ? ' selected' : '') + '>(fleet/default)</option>';
|
|
464
|
+
for (const m of models) {
|
|
465
|
+
const id = m.id || m.name || '';
|
|
466
|
+
if (!id) continue;
|
|
467
|
+
const label = m.name && m.name !== id ? (id + ' — ' + m.name) : id;
|
|
468
|
+
opts += '<option value="' + escHtml(id) + '"' + (id === currentValue ? ' selected' : '') + '>' + escHtml(label) + '</option>';
|
|
469
|
+
}
|
|
470
|
+
// Preserve unknown saved values so a user-set custom ID survives the next save.
|
|
471
|
+
if (currentValue && !models.some(m => (m.id || m.name) === currentValue)) {
|
|
472
|
+
opts += '<option value="' + escHtml(currentValue) + '" selected>' + escHtml(currentValue) + ' (custom — invalid for ' + escHtml(runtimeName) + '?)</option>';
|
|
473
|
+
}
|
|
474
|
+
cell.innerHTML = '<select ' + baseAttrs + ' style="' + baseStyle + '">' + opts + '</select>';
|
|
475
|
+
}
|
|
476
|
+
|
|
412
477
|
function settingsToggle(label, id, checked, hint) {
|
|
413
478
|
return '<div style="display:flex;align-items:center;gap:8px;padding:4px 0">' +
|
|
414
479
|
'<input type="checkbox" id="' + id + '"' + (checked ? ' checked' : '') + ' style="accent-color:var(--blue);width:16px;height:16px;cursor:pointer">' +
|
package/dashboard/layout.html
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
<textarea id="cc-input" rows="2" placeholder="Ask anything or give a command..." style="flex:1;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;resize:none;font-family:inherit" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();ccSend()}"></textarea>
|
|
44
44
|
<button onclick="ccSend()" style="background:var(--blue);color:#fff;border:none;border-radius:6px;padding:8px 14px;font-size:12px;font-weight:600;cursor:pointer;align-self:flex-end">Send</button>
|
|
45
45
|
</div>
|
|
46
|
-
<div style="font-size:9px;color:var(--muted);margin-top:4px">
|
|
46
|
+
<div id="cc-powered-by" style="font-size:9px;color:var(--muted);margin-top:4px">Full minions context. Enter to send, Shift+Enter for newline.</div>
|
|
47
47
|
</div>
|
|
48
48
|
</div>
|
|
49
49
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<div class="cmd-meta" id="cmd-meta" style="display:none"></div>
|
|
11
11
|
<div class="cmd-hints">
|
|
12
12
|
<span style="color:var(--blue);font-weight:600">Command Center</span>
|
|
13
|
-
<span>Ask anything, dispatch work, manage plans
|
|
13
|
+
<span id="cmd-powered-by">Ask anything, dispatch work, manage plans</span>
|
|
14
14
|
<button class="cmd-history-btn" onclick="cmdShowHistory()">Past Commands</button>
|
|
15
15
|
<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--green);border-color:var(--green)" onclick="openQuickNoteModal()">+ Note</button>
|
|
16
16
|
</div>
|
package/dashboard.js
CHANGED
|
@@ -474,7 +474,8 @@ function getStatus() {
|
|
|
474
474
|
decompose: CONFIG.engine?.autoDecompose !== false,
|
|
475
475
|
tempAgents: !!CONFIG.engine?.allowTempAgents,
|
|
476
476
|
inboxThreshold: CONFIG.engine?.inboxConsolidateThreshold || shared.ENGINE_DEFAULTS.inboxConsolidateThreshold,
|
|
477
|
-
|
|
477
|
+
ccCli: shared.resolveCcCli(CONFIG.engine),
|
|
478
|
+
ccModel: shared.resolveCcModel(CONFIG.engine),
|
|
478
479
|
ccEffort: CONFIG.engine?.ccEffort || shared.ENGINE_DEFAULTS.ccEffort,
|
|
479
480
|
},
|
|
480
481
|
initialized: !!(CONFIG.agents && Object.keys(CONFIG.agents).length > 0),
|
|
@@ -4917,13 +4918,58 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4917
4918
|
else if (_validCli(e.ccCli)) config.engine.ccCli = String(e.ccCli);
|
|
4918
4919
|
else _clamped.push(`ccCli: "${e.ccCli}" not registered (kept previous value)`);
|
|
4919
4920
|
}
|
|
4921
|
+
// Validate fleet-level model assignments against the resolved runtime.
|
|
4922
|
+
// This is where the bug bit: defaultCli=copilot + defaultModel=gpt-5.5
|
|
4923
|
+
// (where gpt-5.5 doesn't actually exist) cascaded into every agent
|
|
4924
|
+
// that didn't pin its own model. Reject when the model is known to
|
|
4925
|
+
// belong to a different runtime than the one it'll spawn against.
|
|
4926
|
+
const _engineModelDiscovery = require('./engine/model-discovery');
|
|
4927
|
+
const _engineRuntimes = require('./engine/runtimes');
|
|
4928
|
+
async function _validateFleetModel(modelStr, resolvedRuntime) {
|
|
4929
|
+
if (!modelStr) return null;
|
|
4930
|
+
let knownForResolved = null;
|
|
4931
|
+
try {
|
|
4932
|
+
const list = await _engineModelDiscovery.getRuntimeModels(resolvedRuntime, { config });
|
|
4933
|
+
if (Array.isArray(list?.models) && list.models.length > 0) {
|
|
4934
|
+
knownForResolved = new Set(list.models.map(m => m.id || m.name).filter(Boolean));
|
|
4935
|
+
}
|
|
4936
|
+
} catch { /* unknown runtime */ }
|
|
4937
|
+
if (knownForResolved && !knownForResolved.has(modelStr)) {
|
|
4938
|
+
return `not a valid model for runtime "${resolvedRuntime}" (known: ${[...knownForResolved].slice(0, 4).join(', ')}${knownForResolved.size > 4 ? '…' : ''})`;
|
|
4939
|
+
}
|
|
4940
|
+
if (!knownForResolved) {
|
|
4941
|
+
// Free-text runtime (Claude). Reject only if model belongs to a different runtime's published list.
|
|
4942
|
+
for (const rt of _engineRuntimes.listRuntimes()) {
|
|
4943
|
+
if (rt === resolvedRuntime) continue;
|
|
4944
|
+
try {
|
|
4945
|
+
const otherList = await _engineModelDiscovery.getRuntimeModels(rt, { config });
|
|
4946
|
+
if (Array.isArray(otherList?.models) && otherList.models.some(m => (m.id || m.name) === modelStr)) {
|
|
4947
|
+
return `belongs to runtime "${rt}" but resolved runtime is "${resolvedRuntime}" — incompatible combination`;
|
|
4948
|
+
}
|
|
4949
|
+
} catch { /* skip */ }
|
|
4950
|
+
}
|
|
4951
|
+
}
|
|
4952
|
+
return null;
|
|
4953
|
+
}
|
|
4920
4954
|
if (e.defaultModel !== undefined) {
|
|
4921
4955
|
if (_isClear(e.defaultModel)) delete config.engine.defaultModel;
|
|
4922
|
-
else
|
|
4956
|
+
else {
|
|
4957
|
+
const candidate = String(e.defaultModel);
|
|
4958
|
+
const resolvedCli = config.engine.defaultCli || 'claude';
|
|
4959
|
+
const rejection = await _validateFleetModel(candidate, resolvedCli);
|
|
4960
|
+
if (rejection) _clamped.push(`engine.defaultModel: "${candidate}" ${rejection} — kept previous value`);
|
|
4961
|
+
else config.engine.defaultModel = candidate;
|
|
4962
|
+
}
|
|
4923
4963
|
}
|
|
4924
4964
|
if (e.ccModel !== undefined) {
|
|
4925
4965
|
if (_isClear(e.ccModel)) delete config.engine.ccModel;
|
|
4926
|
-
else
|
|
4966
|
+
else {
|
|
4967
|
+
const candidate = String(e.ccModel);
|
|
4968
|
+
const resolvedCli = config.engine.ccCli || config.engine.defaultCli || 'claude';
|
|
4969
|
+
const rejection = await _validateFleetModel(candidate, resolvedCli);
|
|
4970
|
+
if (rejection) _clamped.push(`engine.ccModel: "${candidate}" ${rejection} — kept previous value`);
|
|
4971
|
+
else config.engine.ccModel = candidate;
|
|
4972
|
+
}
|
|
4927
4973
|
}
|
|
4928
4974
|
if (e.claudeFallbackModel !== undefined) {
|
|
4929
4975
|
if (_isClear(e.claudeFallbackModel)) delete config.engine.claudeFallbackModel;
|
|
@@ -4981,6 +5027,34 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4981
5027
|
}
|
|
4982
5028
|
|
|
4983
5029
|
if (body.agents) {
|
|
5030
|
+
// Cache cross-runtime model lists once per request so we can reject
|
|
5031
|
+
// claude+gpt-* / copilot+claude-* combinations before they crash a
|
|
5032
|
+
// dispatch (see #model-validation: a stray engine.defaultModel='gpt-5.5'
|
|
5033
|
+
// pinned every Claude agent into a 404 spawn loop).
|
|
5034
|
+
const _modelDiscovery = require('./engine/model-discovery');
|
|
5035
|
+
const _runtimeModelsCache = new Map(); // runtimeName → Set<modelId> (or null when unknown / Claude)
|
|
5036
|
+
async function _modelsFor(runtimeName) {
|
|
5037
|
+
if (_runtimeModelsCache.has(runtimeName)) return _runtimeModelsCache.get(runtimeName);
|
|
5038
|
+
let set = null;
|
|
5039
|
+
try {
|
|
5040
|
+
const list = await _modelDiscovery.getRuntimeModels(runtimeName, { config });
|
|
5041
|
+
if (Array.isArray(list?.models) && list.models.length > 0) {
|
|
5042
|
+
set = new Set(list.models.map(m => m.id || m.name).filter(Boolean));
|
|
5043
|
+
}
|
|
5044
|
+
} catch { /* unknown runtime → free-text */ }
|
|
5045
|
+
_runtimeModelsCache.set(runtimeName, set);
|
|
5046
|
+
return set;
|
|
5047
|
+
}
|
|
5048
|
+
// Returns the runtime that "owns" this model, or null if no other
|
|
5049
|
+
// runtime claims it. Catches "claude + gpt-5.5" by spotting that
|
|
5050
|
+
// gpt-5.5 belongs to copilot's list.
|
|
5051
|
+
async function _ownerOfModel(modelId) {
|
|
5052
|
+
for (const rt of require('./engine/runtimes').listRuntimes()) {
|
|
5053
|
+
const set = await _modelsFor(rt);
|
|
5054
|
+
if (set && set.has(modelId)) return rt;
|
|
5055
|
+
}
|
|
5056
|
+
return null;
|
|
5057
|
+
}
|
|
4984
5058
|
for (const [id, updates] of Object.entries(body.agents)) {
|
|
4985
5059
|
if (!config.agents[id]) continue;
|
|
4986
5060
|
if (updates.role !== undefined) config.agents[id].role = String(updates.role);
|
|
@@ -5000,7 +5074,30 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
5000
5074
|
}
|
|
5001
5075
|
if (updates.model !== undefined) {
|
|
5002
5076
|
if (updates.model === '' || updates.model === null) delete config.agents[id].model;
|
|
5003
|
-
else
|
|
5077
|
+
else {
|
|
5078
|
+
const candidate = String(updates.model);
|
|
5079
|
+
const resolvedCli = config.agents[id].cli || config.engine.defaultCli || 'claude';
|
|
5080
|
+
const knownModels = await _modelsFor(resolvedCli);
|
|
5081
|
+
// Two validation paths:
|
|
5082
|
+
// 1. If the runtime publishes a model list, enforce membership.
|
|
5083
|
+
// 2. If the runtime doesn't (Claude), still reject when the
|
|
5084
|
+
// model belongs to a DIFFERENT runtime's list — that's how
|
|
5085
|
+
// we catch claude+gpt-5.5 (gpt-5.5 is in Copilot's list).
|
|
5086
|
+
let rejection = null;
|
|
5087
|
+
if (knownModels && !knownModels.has(candidate)) {
|
|
5088
|
+
rejection = `not a valid model for runtime "${resolvedCli}" (known: ${[...knownModels].slice(0, 4).join(', ')}${knownModels.size > 4 ? '…' : ''})`;
|
|
5089
|
+
} else if (!knownModels) {
|
|
5090
|
+
const owner = await _ownerOfModel(candidate);
|
|
5091
|
+
if (owner && owner !== resolvedCli) {
|
|
5092
|
+
rejection = `belongs to runtime "${owner}" but agent uses "${resolvedCli}" — incompatible combination`;
|
|
5093
|
+
}
|
|
5094
|
+
}
|
|
5095
|
+
if (rejection) {
|
|
5096
|
+
_clamped.push(`agents.${id}.model: "${candidate}" ${rejection} — kept previous value`);
|
|
5097
|
+
} else {
|
|
5098
|
+
config.agents[id].model = candidate;
|
|
5099
|
+
}
|
|
5100
|
+
}
|
|
5004
5101
|
}
|
|
5005
5102
|
if (updates.maxBudgetUsd !== undefined) {
|
|
5006
5103
|
if (updates.maxBudgetUsd === '' || updates.maxBudgetUsd === null) delete config.agents[id].maxBudgetUsd;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1601",
|
|
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"
|