@yemi33/minions 0.1.1588 → 0.1.1589
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 -0
- package/bin/minions.js +5 -3
- package/dashboard/js/settings.js +216 -22
- package/dashboard.js +135 -8
- package/docs/copilot-cli-schema.md +637 -0
- package/docs/copilot-output-sample-claude.jsonl +72 -0
- package/docs/copilot-output-sample-default.jsonl +26 -0
- package/docs/copilot-output-sample-gpt4o.jsonl +23 -0
- package/engine/cli.js +250 -18
- package/engine/lifecycle.js +14 -9
- package/engine/llm.js +346 -94
- package/engine/model-discovery.js +167 -0
- package/engine/preflight.js +247 -19
- package/engine/runtimes/claude.js +413 -0
- package/engine/runtimes/copilot.js +566 -0
- package/engine/runtimes/index.js +61 -0
- package/engine/shared.js +299 -63
- package/engine/spawn-agent.js +265 -181
- package/engine.js +118 -31
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/bin/minions.js
CHANGED
|
@@ -480,7 +480,7 @@ const engineCmds = new Set([
|
|
|
480
480
|
'start', 'stop', 'status', 'pause', 'resume',
|
|
481
481
|
'queue', 'sources', 'discover', 'dispatch',
|
|
482
482
|
'spawn', 'work', 'cleanup', 'mcp-sync', 'plan',
|
|
483
|
-
'kill', 'complete',
|
|
483
|
+
'kill', 'complete', 'config',
|
|
484
484
|
]);
|
|
485
485
|
|
|
486
486
|
if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
@@ -548,14 +548,16 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
|
548
548
|
} else if (cmd === 'add' || cmd === 'remove' || cmd === 'list' || cmd === 'scan') {
|
|
549
549
|
delegate('minions.js', [cmd, ...rest]);
|
|
550
550
|
} else if (cmd === 'restart') {
|
|
551
|
-
// Start both engine and dashboard — the go-to command after a reboot
|
|
551
|
+
// Start both engine and dashboard — the go-to command after a reboot.
|
|
552
|
+
// `--cli` / `--model` flags forward to `engine.js start` so the runtime
|
|
553
|
+
// fleet flips before the daemon spawns (P-6b3f9c2e AC: works on restart).
|
|
552
554
|
ensureInstalled();
|
|
553
555
|
// Stop engine if running (graceful attempt)
|
|
554
556
|
try { execSync(`node "${path.join(MINIONS_HOME, 'engine.js')}" stop`, { stdio: 'ignore', cwd: MINIONS_HOME }); } catch {}
|
|
555
557
|
// Kill all existing engine/dashboard processes — handles crashed engines and orphan dashboards
|
|
556
558
|
killByPort(7331);
|
|
557
559
|
killMinionsProcesses(['engine.js', 'dashboard.js']);
|
|
558
|
-
const engineProc = spawn(process.execPath, [path.join(MINIONS_HOME, 'engine.js'), 'start'], {
|
|
560
|
+
const engineProc = spawn(process.execPath, [path.join(MINIONS_HOME, 'engine.js'), 'start', ...rest], {
|
|
559
561
|
cwd: MINIONS_HOME, stdio: 'ignore', detached: true, windowsHide: true
|
|
560
562
|
});
|
|
561
563
|
engineProc.unref();
|
package/dashboard/js/settings.js
CHANGED
|
@@ -20,11 +20,22 @@ async function openSettings() {
|
|
|
20
20
|
const agents = data.agents || {};
|
|
21
21
|
const t = data.teams || {};
|
|
22
22
|
|
|
23
|
+
// Per-agent override placeholders surface the inherited fleet defaults as
|
|
24
|
+
// muted text — operators see exactly what each agent will resolve to without
|
|
25
|
+
// chasing config files. Empty input clears the override → re-inherit fleet.
|
|
26
|
+
const fleetCliLabel = e.defaultCli || 'claude';
|
|
27
|
+
const fleetModelLabel = e.defaultModel ? String(e.defaultModel) : 'CLI default';
|
|
23
28
|
const agentRows = Object.entries(agents).map(function([id, a]) {
|
|
24
29
|
return '<tr>' +
|
|
25
30
|
'<td style="font-weight:600">' + escHtml(a.emoji || '') + ' ' + escHtml(a.name || id) + '</td>' +
|
|
26
31
|
'<td><input data-agent="' + escHtml(id) + '" data-field="role" value="' + escHtml(a.role || '') + '" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:11px"></td>' +
|
|
27
32
|
'<td><input data-agent="' + escHtml(id) + '" data-field="skills" value="' + escHtml((a.skills || []).join(', ')) + '" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:11px"></td>' +
|
|
33
|
+
'<td data-runtime-cli="' + escHtml(id) + '" style="min-width:110px">' +
|
|
34
|
+
// Initial loading placeholder — initRuntimeFleetUI() replaces this with a
|
|
35
|
+
// <select> populated from /api/runtimes once the registry resolves.
|
|
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
|
+
'</td>' +
|
|
38
|
+
'<td><input data-agent="' + escHtml(id) + '" data-field="model" value="' + escHtml(a.model || '') + '" placeholder="' + escHtml(fleetModelLabel) + ' (fleet)" style="width:120px;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:11px"></td>' +
|
|
28
39
|
'<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>' +
|
|
29
40
|
'</tr>';
|
|
30
41
|
}).join('');
|
|
@@ -112,27 +123,84 @@ async function openSettings() {
|
|
|
112
123
|
settingsField('Decompose', 'set-mt-decompose', (e.maxTurnsByType || {}).decompose || '', '', 'Default: 15') +
|
|
113
124
|
'</div>' +
|
|
114
125
|
|
|
115
|
-
|
|
116
|
-
'<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
'<
|
|
122
|
-
'<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
'<
|
|
128
|
-
|
|
129
|
-
'<
|
|
130
|
-
'<
|
|
131
|
-
|
|
132
|
-
'<option value="high"' + (e.ccEffort === 'high' ? ' selected' : '') + '>High (thorough)</option>' +
|
|
133
|
-
'</select>' +
|
|
134
|
-
'<div style="font-size:9px;color:var(--muted);margin-top:1px">Controls response depth and reasoning effort</div>' +
|
|
126
|
+
// ── Runtime (P-7a5c1f8e) — unified fleet runtime + CC overrides + advanced ──
|
|
127
|
+
'<h3 style="font-size:13px;color:var(--blue);margin-bottom:8px">Runtime</h3>' +
|
|
128
|
+
'<div id="set-runtime-section" style="border:1px solid var(--border);border-radius:6px;padding:10px 12px;margin-bottom:16px">' +
|
|
129
|
+
'<div style="font-size:10px;color:var(--muted);margin-bottom:8px">Single source of truth for which CLI runtime + model the fleet spawns. Per-agent overrides live in the Agents table below.</div>' +
|
|
130
|
+
'<div style="display:grid;grid-template-columns:1fr 2fr;gap:8px;margin-bottom:8px">' +
|
|
131
|
+
'<div>' +
|
|
132
|
+
'<label style="font-size:10px;color:var(--muted);display:block;margin-bottom:2px">Default CLI</label>' +
|
|
133
|
+
'<select id="set-defaultCli" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px">' +
|
|
134
|
+
'<option value="">Loading…</option>' +
|
|
135
|
+
'</select>' +
|
|
136
|
+
'<div style="font-size:9px;color:var(--muted);margin-top:1px">Fleet-wide runtime — registered adapters from <code>/api/runtimes</code></div>' +
|
|
137
|
+
'</div>' +
|
|
138
|
+
'<div>' +
|
|
139
|
+
'<label style="font-size:10px;color:var(--muted);display:block;margin-bottom:2px">Default Model</label>' +
|
|
140
|
+
'<div id="set-defaultModel-wrap"><input id="set-defaultModel" value="' + escHtml(e.defaultModel || '') + '" placeholder="Default (CLI chooses)" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px"></div>' +
|
|
141
|
+
'<div style="font-size:9px;color:var(--muted);margin-top:1px">Empty = let the runtime pick its own default</div>' +
|
|
142
|
+
'</div>' +
|
|
135
143
|
'</div>' +
|
|
144
|
+
// CC overrides — collapsed by default
|
|
145
|
+
'<details id="set-cc-overrides-details"' + ((e.ccCli || e.ccModel) ? ' open' : '') + ' style="margin-top:8px;border-top:1px solid var(--border);padding-top:8px">' +
|
|
146
|
+
'<summary style="cursor:pointer;font-size:11px;color:var(--text);user-select:none">Customize CC separately ' +
|
|
147
|
+
'<span style="font-size:9px;color:var(--muted)">(Command Center + doc-chat use the fleet defaults unless overridden)</span>' +
|
|
148
|
+
'</summary>' +
|
|
149
|
+
'<div style="display:grid;grid-template-columns:1fr 2fr 1fr;gap:8px;margin-top:8px">' +
|
|
150
|
+
'<div>' +
|
|
151
|
+
'<label style="font-size:10px;color:var(--muted);display:block;margin-bottom:2px">CC CLI</label>' +
|
|
152
|
+
'<select id="set-ccCli" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px">' +
|
|
153
|
+
'<option value="">Loading…</option>' +
|
|
154
|
+
'</select>' +
|
|
155
|
+
'<div style="font-size:9px;color:var(--muted);margin-top:1px">Empty = inherit Default CLI</div>' +
|
|
156
|
+
'</div>' +
|
|
157
|
+
'<div>' +
|
|
158
|
+
'<label style="font-size:10px;color:var(--muted);display:block;margin-bottom:2px">CC Model</label>' +
|
|
159
|
+
'<input id="set-ccModel" value="' + escHtml(e.ccModel || '') + '" placeholder="(inherits Default Model)" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px">' +
|
|
160
|
+
'<div style="font-size:9px;color:var(--muted);margin-top:1px">Empty = inherit Default Model</div>' +
|
|
161
|
+
'</div>' +
|
|
162
|
+
'<div>' +
|
|
163
|
+
'<label style="font-size:10px;color:var(--muted);display:block;margin-bottom:2px">Effort</label>' +
|
|
164
|
+
'<select id="set-ccEffort" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px">' +
|
|
165
|
+
'<option value=""' + (!e.ccEffort ? ' selected' : '') + '>Default</option>' +
|
|
166
|
+
'<option value="low"' + (e.ccEffort === 'low' ? ' selected' : '') + '>Low</option>' +
|
|
167
|
+
'<option value="medium"' + (e.ccEffort === 'medium' ? ' selected' : '') + '>Medium</option>' +
|
|
168
|
+
'<option value="high"' + (e.ccEffort === 'high' ? ' selected' : '') + '>High</option>' +
|
|
169
|
+
'</select>' +
|
|
170
|
+
'<div style="font-size:9px;color:var(--muted);margin-top:1px">CC reasoning depth</div>' +
|
|
171
|
+
'</div>' +
|
|
172
|
+
'</div>' +
|
|
173
|
+
'</details>' +
|
|
174
|
+
// Advanced runtime settings — collapsed by default
|
|
175
|
+
'<details id="set-runtime-advanced-details" style="margin-top:8px;border-top:1px solid var(--border);padding-top:8px">' +
|
|
176
|
+
'<summary style="cursor:pointer;font-size:11px;color:var(--text);user-select:none">Advanced runtime settings ' +
|
|
177
|
+
'<span style="font-size:9px;color:var(--muted)">(per-runtime feature flags)</span>' +
|
|
178
|
+
'</summary>' +
|
|
179
|
+
'<div style="display:flex;flex-direction:column;gap:6px;margin-top:8px">' +
|
|
180
|
+
settingsToggle('Claude bare mode', 'set-claudeBareMode', !!e.claudeBareMode, '--bare suppresses CLAUDE.md auto-discovery; pair with explicit ccSystemPrompt or context will be lost') +
|
|
181
|
+
'</div>' +
|
|
182
|
+
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:8px">' +
|
|
183
|
+
settingsField('Claude fallback model', 'set-claudeFallbackModel', e.claudeFallbackModel || '', '', 'Used by --fallback-model on rate-limit / overload (Claude only)') +
|
|
184
|
+
settingsField('Max budget (USD)', 'set-maxBudgetUsd', e.maxBudgetUsd != null ? String(e.maxBudgetUsd) : '', '', 'Fleet ceiling for --max-budget-usd. 0 is a valid cap (read-only / dry-run). Empty = no cap. Claude only.') +
|
|
185
|
+
'</div>' +
|
|
186
|
+
'<div style="display:flex;flex-direction:column;gap:6px;margin-top:8px">' +
|
|
187
|
+
// Tooltip on copilotDisableBuiltinMcps MUST warn about the split-brain risk
|
|
188
|
+
settingsToggle('Copilot: disable built-in MCPs', 'set-copilotDisableBuiltinMcps', e.copilotDisableBuiltinMcps !== false,
|
|
189
|
+
'⚠ When OFF, Copilot agents can autonomously create PRs/labels/comments via the github-mcp-server, bypassing pull-requests.json tracking — Minions and Copilot end up with split views of the same PR. Keep ON unless you understand the risk.') +
|
|
190
|
+
settingsToggle('Copilot: suppress AGENTS.md', 'set-copilotSuppressAgentsMd', e.copilotSuppressAgentsMd !== false, '--no-custom-instructions: stops AGENTS.md auto-load from fighting Minions playbook prompts') +
|
|
191
|
+
settingsToggle('Copilot: reasoning summaries', 'set-copilotReasoningSummaries', !!e.copilotReasoningSummaries, '--enable-reasoning-summaries (Anthropic-family models only)') +
|
|
192
|
+
settingsToggle('Disable model discovery', 'set-disableModelDiscovery', !!e.disableModelDiscovery, 'Skip /api/runtimes/<name>/models REST calls fleet-wide. Settings UI falls back to free-text.') +
|
|
193
|
+
'</div>' +
|
|
194
|
+
'<div style="display:grid;grid-template-columns:1fr 3fr;gap:8px;margin-top:8px">' +
|
|
195
|
+
'<div>' +
|
|
196
|
+
'<label style="font-size:10px;color:var(--muted);display:block;margin-bottom:2px">Copilot stream</label>' +
|
|
197
|
+
'<select id="set-copilotStreamMode" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px">' +
|
|
198
|
+
'<option value="on"' + ((e.copilotStreamMode || 'on') === 'on' ? ' selected' : '') + '>on (incremental)</option>' +
|
|
199
|
+
'<option value="off"' + (e.copilotStreamMode === 'off' ? ' selected' : '') + '>off (batched)</option>' +
|
|
200
|
+
'</select>' +
|
|
201
|
+
'</div>' +
|
|
202
|
+
'</div>' +
|
|
203
|
+
'</details>' +
|
|
136
204
|
'</div>' +
|
|
137
205
|
|
|
138
206
|
'<h3 style="font-size:13px;color:var(--blue);margin-bottom:8px">Teams Integration</h3>' +
|
|
@@ -175,8 +243,9 @@ async function openSettings() {
|
|
|
175
243
|
'</div>' +
|
|
176
244
|
|
|
177
245
|
'<h3 style="font-size:13px;color:var(--blue);margin-bottom:8px">Agents</h3>' +
|
|
246
|
+
'<div style="font-size:10px;color:var(--muted);margin-bottom:6px">CLI / Model placeholders show the fleet default each agent will inherit. Pick a value to pin per-agent; clear to re-inherit.</div>' +
|
|
178
247
|
'<table style="width:100%;border-collapse:collapse;margin-bottom:16px;font-size:11px">' +
|
|
179
|
-
'<tr style="text-align:left;color:var(--muted)"><th style="padding:4px">Agent</th><th style="padding:4px">Role</th><th style="padding:4px">Skills</th><th style="padding:4px">Budget $/mo</th></tr>' +
|
|
248
|
+
'<tr style="text-align:left;color:var(--muted)"><th style="padding:4px">Agent</th><th style="padding:4px">Role</th><th style="padding:4px">Skills</th><th style="padding:4px">CLI</th><th style="padding:4px">Model</th><th style="padding:4px">Budget $/mo</th></tr>' +
|
|
180
249
|
agentRows +
|
|
181
250
|
'</table>' +
|
|
182
251
|
|
|
@@ -226,6 +295,118 @@ async function openSettings() {
|
|
|
226
295
|
warn.style.display = 'none';
|
|
227
296
|
}
|
|
228
297
|
});
|
|
298
|
+
|
|
299
|
+
// ── Runtime fleet wiring (P-7a5c1f8e) ──────────────────────────────────────
|
|
300
|
+
// 1. Load registered runtimes into the defaultCli + ccCli dropdowns.
|
|
301
|
+
// 2. Load models for the selected defaultCli into the defaultModel input.
|
|
302
|
+
// 3. On defaultCli change → re-fetch models so the input never shows stale list.
|
|
303
|
+
// The same pattern wires ccCli → ccModel; ccCli inherits defaultCli when unset.
|
|
304
|
+
initRuntimeFleetUI(e, agents);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function initRuntimeFleetUI(engineCfg, agentsCfg) {
|
|
308
|
+
const cliSelect = document.getElementById('set-defaultCli');
|
|
309
|
+
const ccCliSelect = document.getElementById('set-ccCli');
|
|
310
|
+
if (!cliSelect || !ccCliSelect) return;
|
|
311
|
+
|
|
312
|
+
// Fetch the registry; render an empty fallback on failure so the rest of the
|
|
313
|
+
// settings panel still works.
|
|
314
|
+
let runtimes = [];
|
|
315
|
+
try {
|
|
316
|
+
const r = await fetch('/api/runtimes');
|
|
317
|
+
const d = await r.json();
|
|
318
|
+
runtimes = Array.isArray(d.runtimes) ? d.runtimes : [];
|
|
319
|
+
} catch { /* ignore — we'll surface a free-text-only path below */ }
|
|
320
|
+
|
|
321
|
+
// Always include 'claude' as a fallback option even if /api/runtimes is empty;
|
|
322
|
+
// legacy installs without the registry endpoint should still see something pickable.
|
|
323
|
+
const names = runtimes.length ? runtimes.map(rt => rt.name) : ['claude'];
|
|
324
|
+
const currentDefault = engineCfg.defaultCli || 'claude';
|
|
325
|
+
const currentCc = engineCfg.ccCli || '';
|
|
326
|
+
cliSelect.innerHTML = names.map(n =>
|
|
327
|
+
'<option value="' + escHtml(n) + '"' + (n === currentDefault ? ' selected' : '') + '>' + escHtml(n) + '</option>'
|
|
328
|
+
).join('');
|
|
329
|
+
ccCliSelect.innerHTML =
|
|
330
|
+
'<option value=""' + (!currentCc ? ' selected' : '') + '>Inherit Default CLI</option>' +
|
|
331
|
+
names.map(n =>
|
|
332
|
+
'<option value="' + escHtml(n) + '"' + (n === currentCc ? ' selected' : '') + '>' + escHtml(n) + '</option>'
|
|
333
|
+
).join('');
|
|
334
|
+
|
|
335
|
+
// Hydrate per-agent CLI dropdowns now that we know the registered names. The
|
|
336
|
+
// Agents table renders cells with `data-runtime-cli="<id>"` as a hook.
|
|
337
|
+
const cliCells = document.querySelectorAll('[data-runtime-cli]');
|
|
338
|
+
for (const cell of cliCells) {
|
|
339
|
+
const agentId = cell.getAttribute('data-runtime-cli');
|
|
340
|
+
const agent = (agentsCfg || {})[agentId] || {};
|
|
341
|
+
const current = agent.cli || '';
|
|
342
|
+
cell.innerHTML =
|
|
343
|
+
'<select data-agent="' + escHtml(agentId) + '" data-field="cli" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:11px">' +
|
|
344
|
+
'<option value=""' + (!current ? ' selected' : '') + '>(fleet default)</option>' +
|
|
345
|
+
names.map(n =>
|
|
346
|
+
'<option value="' + escHtml(n) + '"' + (n === current ? ' selected' : '') + '>' + escHtml(n) + '</option>'
|
|
347
|
+
).join('') +
|
|
348
|
+
'</select>';
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Models load for the resolved default + CC CLIs. ccCli falls back to
|
|
352
|
+
// defaultCli when unset — same rule as resolveCcCli().
|
|
353
|
+
loadModelsForRuntime(cliSelect.value, 'set-defaultModel', engineCfg.defaultModel || '');
|
|
354
|
+
loadModelsForRuntime(currentCc || cliSelect.value, 'set-ccModel', engineCfg.ccModel || '');
|
|
355
|
+
|
|
356
|
+
// CLI change → re-fetch models. NEVER carry the previous runtime's list over.
|
|
357
|
+
cliSelect.addEventListener('change', () => {
|
|
358
|
+
loadModelsForRuntime(cliSelect.value, 'set-defaultModel', '');
|
|
359
|
+
if (!ccCliSelect.value) {
|
|
360
|
+
// CC inherits defaultCli — its model list must follow.
|
|
361
|
+
loadModelsForRuntime(cliSelect.value, 'set-ccModel', '');
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
ccCliSelect.addEventListener('change', () => {
|
|
365
|
+
const target = ccCliSelect.value || cliSelect.value;
|
|
366
|
+
loadModelsForRuntime(target, 'set-ccModel', '');
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Replace the input/select at `inputId` with a dropdown when the runtime
|
|
372
|
+
* exposes a model list, or a free-text input when `{ models: null }` (e.g.
|
|
373
|
+
* Claude or model-discovery disabled). The "Default (CLI chooses)" option is
|
|
374
|
+
* always present and submits empty string.
|
|
375
|
+
*/
|
|
376
|
+
async function loadModelsForRuntime(runtimeName, inputId, currentValue) {
|
|
377
|
+
const wrap = document.getElementById(inputId)?.parentElement;
|
|
378
|
+
if (!wrap) return;
|
|
379
|
+
if (!runtimeName) {
|
|
380
|
+
wrap.innerHTML = '<input id="' + inputId + '" value="' + escHtml(currentValue || '') + '" placeholder="(no runtime selected)" disabled style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--muted);font-size:12px">';
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
let payload = { models: null };
|
|
384
|
+
try {
|
|
385
|
+
const res = await fetch('/api/runtimes/' + encodeURIComponent(runtimeName) + '/models');
|
|
386
|
+
if (res.ok) payload = await res.json();
|
|
387
|
+
} catch { /* fall through to free-text */ }
|
|
388
|
+
|
|
389
|
+
const models = Array.isArray(payload.models) ? payload.models : null;
|
|
390
|
+
if (!models || models.length === 0) {
|
|
391
|
+
// Free-text fallback — let the user type anything (custom Anthropic /
|
|
392
|
+
// OpenAI model IDs, future models, etc.).
|
|
393
|
+
wrap.innerHTML = '<input id="' + inputId + '" value="' + escHtml(currentValue || '') + '" placeholder="Default (CLI chooses)" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px">';
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
// Dropdown. The first option submits empty string → "Default (CLI chooses)".
|
|
397
|
+
let opts = '<option value=""' + (!currentValue ? ' selected' : '') + '>Default (CLI chooses)</option>';
|
|
398
|
+
for (const m of models) {
|
|
399
|
+
const id = m.id || m.name || '';
|
|
400
|
+
if (!id) continue;
|
|
401
|
+
const label = m.name && m.name !== id ? (id + ' — ' + m.name) : id;
|
|
402
|
+
opts += '<option value="' + escHtml(id) + '"' + (id === currentValue ? ' selected' : '') + '>' + escHtml(label) + '</option>';
|
|
403
|
+
}
|
|
404
|
+
// If the current value isn't in the model list (custom / older choice),
|
|
405
|
+
// surface it as a selectable option so the user doesn't lose it on next save.
|
|
406
|
+
if (currentValue && !models.some(m => (m.id || m.name) === currentValue)) {
|
|
407
|
+
opts += '<option value="' + escHtml(currentValue) + '" selected>' + escHtml(currentValue) + ' (custom)</option>';
|
|
408
|
+
}
|
|
409
|
+
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>';
|
|
229
410
|
}
|
|
230
411
|
|
|
231
412
|
function settingsToggle(label, id, checked, hint) {
|
|
@@ -284,8 +465,21 @@ async function saveSettings() {
|
|
|
284
465
|
agentBusyReassignMs: document.getElementById('set-agentBusyReassignMs').value,
|
|
285
466
|
ignoredCommentAuthors: document.getElementById('set-ignoredCommentAuthors').value,
|
|
286
467
|
versionCheckInterval: document.getElementById('set-versionCheckInterval').value,
|
|
287
|
-
|
|
468
|
+
// Runtime fleet (P-7a5c1f8e). Empty strings are intentional — they signal
|
|
469
|
+
// "clear this override". The server deletes the key from config.engine.
|
|
470
|
+
defaultCli: (document.getElementById('set-defaultCli')?.value ?? '').trim(),
|
|
471
|
+
defaultModel: (document.getElementById('set-defaultModel')?.value ?? '').trim(),
|
|
472
|
+
ccCli: (document.getElementById('set-ccCli')?.value ?? '').trim(),
|
|
473
|
+
ccModel: (document.getElementById('set-ccModel')?.value ?? '').trim(),
|
|
288
474
|
ccEffort: document.getElementById('set-ccEffort').value || null,
|
|
475
|
+
claudeBareMode: !!document.getElementById('set-claudeBareMode')?.checked,
|
|
476
|
+
claudeFallbackModel: (document.getElementById('set-claudeFallbackModel')?.value ?? '').trim(),
|
|
477
|
+
copilotDisableBuiltinMcps: !!document.getElementById('set-copilotDisableBuiltinMcps')?.checked,
|
|
478
|
+
copilotSuppressAgentsMd: !!document.getElementById('set-copilotSuppressAgentsMd')?.checked,
|
|
479
|
+
copilotStreamMode: document.getElementById('set-copilotStreamMode')?.value || 'on',
|
|
480
|
+
copilotReasoningSummaries: !!document.getElementById('set-copilotReasoningSummaries')?.checked,
|
|
481
|
+
maxBudgetUsd: (document.getElementById('set-maxBudgetUsd')?.value ?? '').trim(),
|
|
482
|
+
disableModelDiscovery: !!document.getElementById('set-disableModelDiscovery')?.checked,
|
|
289
483
|
maxTurnsByType: (function() {
|
|
290
484
|
var mbt = {};
|
|
291
485
|
var types = ['explore', 'ask', 'review', 'implement', 'fix', 'test', 'verify', 'plan', 'decompose'];
|
package/dashboard.js
CHANGED
|
@@ -551,6 +551,10 @@ const CC_STREAM_HEARTBEAT_MS = 15000; // keep streaming responses alive across p
|
|
|
551
551
|
const DOC_CHAT_TIMEOUT_MS = 360000; // allow longer doc-chat turns before timing out server-side
|
|
552
552
|
const CC_STREAM_REATTACH_GRACE_MS = 60000; // keep CC job alive briefly after disconnect so the UI can reattach
|
|
553
553
|
const CC_STREAM_DONE_RETENTION_MS = 30000; // retain final payload briefly so reconnect can still receive it
|
|
554
|
+
// Doc-chat is interactive — long-doc edits with multi-step Read+Write tool use can run
|
|
555
|
+
// 4–5 min on `canEdit:true` paths. CC's default 2-min timeout was killing legitimate
|
|
556
|
+
// edits mid-stream. Pinned to 6 min as the bounded but generous ceiling.
|
|
557
|
+
const DOC_CHAT_TIMEOUT_MS = 360000;
|
|
554
558
|
function _releaseCCTab(tabId) { ccInFlightTabs.delete(tabId); ccInFlightAborts.delete(tabId); }
|
|
555
559
|
function _getCcLiveStream(tabId) {
|
|
556
560
|
return ccLiveStreams.get(tabId) || null;
|
|
@@ -1230,6 +1234,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
|
|
|
1230
1234
|
if (sessionId && maxTurns > 1) {
|
|
1231
1235
|
const p1 = llm.callLLM(buildPrompt({ includePreamble: false }), '', {
|
|
1232
1236
|
timeout, label, model, maxTurns, allowedTools, sessionId, effort: ccEffort, direct: true,
|
|
1237
|
+
engineConfig: CONFIG.engine,
|
|
1233
1238
|
});
|
|
1234
1239
|
if (onAbortReady) onAbortReady(p1.abort);
|
|
1235
1240
|
result = await p1;
|
|
@@ -1241,9 +1246,10 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
|
|
|
1241
1246
|
}
|
|
1242
1247
|
|
|
1243
1248
|
// No text — distinguish "session exists but call failed" (e.g. tool timeout)
|
|
1244
|
-
// from "session is truly dead" (no sessionId
|
|
1245
|
-
|
|
1246
|
-
if
|
|
1249
|
+
// from "session is truly dead" (no sessionId in the parsed output).
|
|
1250
|
+
// Per P-5e1b7a3c: trust the runtime adapter's parseOutput — if it found a
|
|
1251
|
+
// sessionId the session is alive; if not, treat it as dead and retry fresh.
|
|
1252
|
+
if (result.sessionId !== null) {
|
|
1247
1253
|
console.log(`[${label}] Resume call failed (code=${result.code}, empty=${!result.text}) but session is still valid — preserving session for retry`);
|
|
1248
1254
|
updateSession(store, sessionKey, result.sessionId || sessionId, true);
|
|
1249
1255
|
return result;
|
|
@@ -1265,6 +1271,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
|
|
|
1265
1271
|
const freshPrompt = buildPrompt();
|
|
1266
1272
|
const p2 = llm.callLLM(freshPrompt, CC_STATIC_SYSTEM_PROMPT, {
|
|
1267
1273
|
timeout, label, model, maxTurns, allowedTools, effort: ccEffort, direct: true,
|
|
1274
|
+
engineConfig: CONFIG.engine,
|
|
1268
1275
|
});
|
|
1269
1276
|
if (onAbortReady) onAbortReady(p2.abort);
|
|
1270
1277
|
result = await p2;
|
|
@@ -1281,6 +1288,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
|
|
|
1281
1288
|
await new Promise(r => setTimeout(r, 2000));
|
|
1282
1289
|
const p3 = llm.callLLM(freshPrompt, CC_STATIC_SYSTEM_PROMPT, {
|
|
1283
1290
|
timeout, label, model, maxTurns, allowedTools, effort: ccEffort, direct: true,
|
|
1291
|
+
engineConfig: CONFIG.engine,
|
|
1284
1292
|
});
|
|
1285
1293
|
if (onAbortReady) onAbortReady(p3.abort);
|
|
1286
1294
|
result = await p3;
|
|
@@ -1311,6 +1319,7 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
|
|
|
1311
1319
|
if (sessionId && maxTurns > 1) {
|
|
1312
1320
|
const p1 = llm.callLLMStreaming(buildPrompt({ includePreamble: false }), '', {
|
|
1313
1321
|
timeout, label, model, maxTurns, allowedTools, sessionId, effort: ccEffort, direct: true,
|
|
1322
|
+
engineConfig: CONFIG.engine,
|
|
1314
1323
|
onChunk,
|
|
1315
1324
|
onToolUse,
|
|
1316
1325
|
});
|
|
@@ -1323,8 +1332,10 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
|
|
|
1323
1332
|
return result;
|
|
1324
1333
|
}
|
|
1325
1334
|
|
|
1326
|
-
|
|
1327
|
-
|
|
1335
|
+
// Per P-5e1b7a3c: parsedOutput.sessionId !== null means the runtime adapter
|
|
1336
|
+
// successfully captured a session — preserve it for retry. null means the
|
|
1337
|
+
// session is truly dead (or never started); rotate it.
|
|
1338
|
+
if (result.sessionId !== null) {
|
|
1328
1339
|
console.log(`[${label}] Resume call failed (code=${result.code}, empty=${!result.text}) but session is still valid — preserving session for retry`);
|
|
1329
1340
|
updateSession(store, sessionKey, result.sessionId || sessionId, true);
|
|
1330
1341
|
return result;
|
|
@@ -1344,6 +1355,7 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
|
|
|
1344
1355
|
const freshPrompt = buildPrompt();
|
|
1345
1356
|
const p2 = llm.callLLMStreaming(freshPrompt, CC_STATIC_SYSTEM_PROMPT, {
|
|
1346
1357
|
timeout, label, model, maxTurns, allowedTools, effort: ccEffort, direct: true,
|
|
1358
|
+
engineConfig: CONFIG.engine,
|
|
1347
1359
|
onChunk,
|
|
1348
1360
|
onToolUse,
|
|
1349
1361
|
});
|
|
@@ -1361,6 +1373,7 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
|
|
|
1361
1373
|
await new Promise(r => setTimeout(r, 2000));
|
|
1362
1374
|
const p3 = llm.callLLMStreaming(freshPrompt, CC_STATIC_SYSTEM_PROMPT, {
|
|
1363
1375
|
timeout, label, model, maxTurns, allowedTools, effort: ccEffort, direct: true,
|
|
1376
|
+
engineConfig: CONFIG.engine,
|
|
1364
1377
|
onChunk,
|
|
1365
1378
|
onToolUse,
|
|
1366
1379
|
});
|
|
@@ -1429,6 +1442,7 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
|
|
|
1429
1442
|
timeout: DOC_CHAT_TIMEOUT_MS,
|
|
1430
1443
|
allowedTools: canEdit ? 'Read,Write,Edit,Glob,Grep' : 'Read,Glob,Grep',
|
|
1431
1444
|
maxTurns: canEdit ? 25 : 10,
|
|
1445
|
+
timeout: DOC_CHAT_TIMEOUT_MS,
|
|
1432
1446
|
skipStatePreamble: true,
|
|
1433
1447
|
...(model ? { model } : {}),
|
|
1434
1448
|
onAbortReady,
|
|
@@ -1478,6 +1492,7 @@ async function ccDocCallStreaming({ message, document, title, filePath, selectio
|
|
|
1478
1492
|
timeout: DOC_CHAT_TIMEOUT_MS,
|
|
1479
1493
|
allowedTools: canEdit ? 'Read,Write,Edit,Glob,Grep' : 'Read,Glob,Grep',
|
|
1480
1494
|
maxTurns: canEdit ? 25 : 10,
|
|
1495
|
+
timeout: DOC_CHAT_TIMEOUT_MS,
|
|
1481
1496
|
skipStatePreamble: true,
|
|
1482
1497
|
...(model ? { model } : {}),
|
|
1483
1498
|
onAbortReady,
|
|
@@ -4525,6 +4540,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4525
4540
|
timeout: 900000, label: 'command-center', model: streamModel, maxTurns: ccMaxTurns,
|
|
4526
4541
|
allowedTools: 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch',
|
|
4527
4542
|
sessionId, effort: streamEffort, direct: true,
|
|
4543
|
+
engineConfig: CONFIG.engine,
|
|
4528
4544
|
onChunk: (text) => {
|
|
4529
4545
|
const actIdx = findCCActionsDelimiter(text);
|
|
4530
4546
|
const display = actIdx >= 0 ? text.slice(0, actIdx).trim() : text;
|
|
@@ -4554,6 +4570,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4554
4570
|
timeout: 900000, label: 'command-center', model: streamModel, maxTurns: ccMaxTurns,
|
|
4555
4571
|
allowedTools: 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch',
|
|
4556
4572
|
effort: streamEffort, direct: true,
|
|
4573
|
+
engineConfig: CONFIG.engine,
|
|
4557
4574
|
onChunk: (text) => {
|
|
4558
4575
|
const actIdx = findCCActionsDelimiter(text);
|
|
4559
4576
|
const display = actIdx >= 0 ? text.slice(0, actIdx).trim() : text;
|
|
@@ -4876,10 +4893,57 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4876
4893
|
delete config.engine.adoPollCommentsEvery;
|
|
4877
4894
|
// String fields
|
|
4878
4895
|
if (e.worktreeRoot !== undefined) config.engine.worktreeRoot = String(e.worktreeRoot || D.worktreeRoot);
|
|
4879
|
-
|
|
4896
|
+
|
|
4897
|
+
// ── Runtime fleet (P-7a5c1f8e) ─────────────────────────────────────
|
|
4898
|
+
// Empty string clears the override — the dashboard's "Default (CLI
|
|
4899
|
+
// chooses)" option submits '' and we must persist that as "unset".
|
|
4900
|
+
// Validate `defaultCli` and `ccCli` against the runtime registry so a
|
|
4901
|
+
// typo in the dashboard can't pin the fleet to a non-existent runtime.
|
|
4902
|
+
const _isClear = (v) => v === '' || v === null;
|
|
4903
|
+
let _registeredCliNames = null;
|
|
4904
|
+
const _validCli = (name) => {
|
|
4905
|
+
if (_registeredCliNames == null) {
|
|
4906
|
+
try { _registeredCliNames = require('./engine/runtimes').listRuntimes(); }
|
|
4907
|
+
catch { _registeredCliNames = []; }
|
|
4908
|
+
}
|
|
4909
|
+
return _registeredCliNames.length === 0 || _registeredCliNames.includes(String(name));
|
|
4910
|
+
};
|
|
4911
|
+
if (e.defaultCli !== undefined) {
|
|
4912
|
+
if (_isClear(e.defaultCli)) delete config.engine.defaultCli;
|
|
4913
|
+
else if (_validCli(e.defaultCli)) config.engine.defaultCli = String(e.defaultCli);
|
|
4914
|
+
else _clamped.push(`defaultCli: "${e.defaultCli}" not registered (kept previous value)`);
|
|
4915
|
+
}
|
|
4916
|
+
if (e.ccCli !== undefined) {
|
|
4917
|
+
if (_isClear(e.ccCli)) delete config.engine.ccCli;
|
|
4918
|
+
else if (_validCli(e.ccCli)) config.engine.ccCli = String(e.ccCli);
|
|
4919
|
+
else _clamped.push(`ccCli: "${e.ccCli}" not registered (kept previous value)`);
|
|
4920
|
+
}
|
|
4921
|
+
if (e.defaultModel !== undefined) {
|
|
4922
|
+
if (_isClear(e.defaultModel)) delete config.engine.defaultModel;
|
|
4923
|
+
else config.engine.defaultModel = String(e.defaultModel);
|
|
4924
|
+
}
|
|
4880
4925
|
if (e.ccModel !== undefined) {
|
|
4881
|
-
|
|
4882
|
-
config.engine.ccModel =
|
|
4926
|
+
if (_isClear(e.ccModel)) delete config.engine.ccModel;
|
|
4927
|
+
else config.engine.ccModel = String(e.ccModel);
|
|
4928
|
+
}
|
|
4929
|
+
if (e.claudeFallbackModel !== undefined) {
|
|
4930
|
+
if (_isClear(e.claudeFallbackModel)) delete config.engine.claudeFallbackModel;
|
|
4931
|
+
else config.engine.claudeFallbackModel = String(e.claudeFallbackModel);
|
|
4932
|
+
}
|
|
4933
|
+
if (e.copilotStreamMode !== undefined) {
|
|
4934
|
+
const valid = ['on', 'off'];
|
|
4935
|
+
if (_isClear(e.copilotStreamMode)) delete config.engine.copilotStreamMode;
|
|
4936
|
+
else if (valid.includes(e.copilotStreamMode)) config.engine.copilotStreamMode = e.copilotStreamMode;
|
|
4937
|
+
else _clamped.push(`copilotStreamMode: "${e.copilotStreamMode}" not in [on, off] (kept previous value)`);
|
|
4938
|
+
}
|
|
4939
|
+
// maxBudgetUsd uses ?? semantics — 0 is a valid cap (read-only / dry-run agents).
|
|
4940
|
+
if (e.maxBudgetUsd !== undefined) {
|
|
4941
|
+
if (_isClear(e.maxBudgetUsd)) delete config.engine.maxBudgetUsd;
|
|
4942
|
+
else {
|
|
4943
|
+
const n = Number(e.maxBudgetUsd);
|
|
4944
|
+
if (Number.isFinite(n) && n >= 0) config.engine.maxBudgetUsd = n;
|
|
4945
|
+
else _clamped.push(`maxBudgetUsd: "${e.maxBudgetUsd}" must be ≥ 0 (kept previous value)`);
|
|
4946
|
+
}
|
|
4883
4947
|
}
|
|
4884
4948
|
if (e.ccEffort !== undefined) {
|
|
4885
4949
|
const valid = [null, 'low', 'medium', 'high'];
|
|
@@ -4927,6 +4991,31 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4927
4991
|
if (val === undefined || isNaN(val)) delete config.agents[id].monthlyBudgetUsd;
|
|
4928
4992
|
else config.agents[id].monthlyBudgetUsd = Math.max(0, val);
|
|
4929
4993
|
}
|
|
4994
|
+
// Per-agent runtime overrides (P-7a5c1f8e). Empty string clears
|
|
4995
|
+
// the override so the agent inherits the fleet default; validated
|
|
4996
|
+
// CLI values pin the agent to a specific runtime. `0` is a valid
|
|
4997
|
+
// maxBudgetUsd (read-only / dry-run agents).
|
|
4998
|
+
if (updates.cli !== undefined) {
|
|
4999
|
+
if (updates.cli === '' || updates.cli === null) delete config.agents[id].cli;
|
|
5000
|
+
else config.agents[id].cli = String(updates.cli);
|
|
5001
|
+
}
|
|
5002
|
+
if (updates.model !== undefined) {
|
|
5003
|
+
if (updates.model === '' || updates.model === null) delete config.agents[id].model;
|
|
5004
|
+
else config.agents[id].model = String(updates.model);
|
|
5005
|
+
}
|
|
5006
|
+
if (updates.maxBudgetUsd !== undefined) {
|
|
5007
|
+
if (updates.maxBudgetUsd === '' || updates.maxBudgetUsd === null) delete config.agents[id].maxBudgetUsd;
|
|
5008
|
+
else {
|
|
5009
|
+
const n = Number(updates.maxBudgetUsd);
|
|
5010
|
+
if (Number.isFinite(n) && n >= 0) config.agents[id].maxBudgetUsd = n;
|
|
5011
|
+
}
|
|
5012
|
+
}
|
|
5013
|
+
if (updates.bareMode !== undefined) {
|
|
5014
|
+
// Boolean override — explicit false should override engine.claudeBareMode=true,
|
|
5015
|
+
// so we accept all three states (true, false, "unset" via empty/null).
|
|
5016
|
+
if (updates.bareMode === '' || updates.bareMode === null) delete config.agents[id].bareMode;
|
|
5017
|
+
else config.agents[id].bareMode = !!updates.bareMode;
|
|
5018
|
+
}
|
|
4930
5019
|
}
|
|
4931
5020
|
}
|
|
4932
5021
|
|
|
@@ -5749,6 +5838,44 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
5749
5838
|
}},
|
|
5750
5839
|
{ method: 'POST', path: '/api/engine/restart', desc: 'Force-kill engine and restart immediately', handler: handleEngineRestart },
|
|
5751
5840
|
|
|
5841
|
+
// Runtimes (CLI fleet) — model discovery + capability surface
|
|
5842
|
+
{ method: 'GET', path: '/api/runtimes', desc: 'List registered CLI runtimes and their capability flags', handler: (req, res) => {
|
|
5843
|
+
const md = require('./engine/model-discovery');
|
|
5844
|
+
return jsonReply(res, 200, { runtimes: md.listAllRuntimes() }, req);
|
|
5845
|
+
}},
|
|
5846
|
+
{ method: 'POST', path: /^\/api\/runtimes\/([\w-]+)\/models\/refresh$/, desc: 'Invalidate the models cache for a runtime and re-fetch', handler: async (req, res, match) => {
|
|
5847
|
+
const md = require('./engine/model-discovery');
|
|
5848
|
+
const name = match[1];
|
|
5849
|
+
try {
|
|
5850
|
+
md.invalidateRuntimeModelsCache(name);
|
|
5851
|
+
} catch (e) {
|
|
5852
|
+
if (/Unknown runtime/.test(e.message || '')) return jsonReply(res, 404, { error: e.message }, req);
|
|
5853
|
+
return jsonReply(res, 500, { error: String(e.message || e) }, req);
|
|
5854
|
+
}
|
|
5855
|
+
let payload;
|
|
5856
|
+
try {
|
|
5857
|
+
reloadConfig();
|
|
5858
|
+
payload = await md.getRuntimeModels(name, { force: true, config: CONFIG });
|
|
5859
|
+
} catch (e) {
|
|
5860
|
+
if (/Unknown runtime/.test(e.message || '')) return jsonReply(res, 404, { error: e.message }, req);
|
|
5861
|
+
return jsonReply(res, 500, { error: String(e.message || e) }, req);
|
|
5862
|
+
}
|
|
5863
|
+
return jsonReply(res, 200, payload, req);
|
|
5864
|
+
}},
|
|
5865
|
+
{ method: 'GET', path: /^\/api\/runtimes\/([\w-]+)\/models$/, desc: 'Get cached or fresh model list for a runtime', handler: async (req, res, match) => {
|
|
5866
|
+
const md = require('./engine/model-discovery');
|
|
5867
|
+
const name = match[1];
|
|
5868
|
+
let payload;
|
|
5869
|
+
try {
|
|
5870
|
+
reloadConfig();
|
|
5871
|
+
payload = await md.getRuntimeModels(name, { config: CONFIG });
|
|
5872
|
+
} catch (e) {
|
|
5873
|
+
if (/Unknown runtime/.test(e.message || '')) return jsonReply(res, 404, { error: e.message }, req);
|
|
5874
|
+
return jsonReply(res, 500, { error: String(e.message || e) }, req);
|
|
5875
|
+
}
|
|
5876
|
+
return jsonReply(res, 200, payload, req);
|
|
5877
|
+
}},
|
|
5878
|
+
|
|
5752
5879
|
// Settings
|
|
5753
5880
|
{ method: 'GET', path: '/api/settings', desc: 'Return current engine + claude + routing config', handler: handleSettingsRead },
|
|
5754
5881
|
{ method: 'POST', path: '/api/settings', desc: 'Update engine + claude + agent + teams + projects config', params: 'engine?, claude?, agents?, teams?, projects?', handler: handleSettingsUpdate },
|