@yemi33/minions 0.1.2376 → 0.1.2377
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/dashboard/js/settings.js +23 -30
- package/dashboard.js +21 -30
- package/engine/llm.js +2 -1
- package/engine/model-discovery.js +15 -2
- package/engine/runtimes/codex.js +81 -7
- package/package.json +1 -1
package/dashboard/js/settings.js
CHANGED
|
@@ -890,13 +890,11 @@ async function initRuntimeFleetUI(engineCfg, agentsCfg) {
|
|
|
890
890
|
}
|
|
891
891
|
|
|
892
892
|
/**
|
|
893
|
-
*
|
|
894
|
-
*
|
|
895
|
-
* Claude or model-discovery disabled). The "Default (CLI chooses)" option is
|
|
896
|
-
* always present and submits empty string.
|
|
893
|
+
* Render an editable model field backed by discovered-model suggestions.
|
|
894
|
+
* Discovery is advisory: staged/future/custom model IDs must remain typeable.
|
|
897
895
|
*/
|
|
898
|
-
async function loadModelsForRuntime(runtimeName, inputId, currentValue) {
|
|
899
|
-
const wrap = document.getElementById(inputId)?.parentElement;
|
|
896
|
+
async function loadModelsForRuntime(runtimeName, inputId, currentValue, forceRefresh) {
|
|
897
|
+
const wrap = document.getElementById(inputId + '-wrap') || document.getElementById(inputId)?.parentElement;
|
|
900
898
|
if (!wrap) return;
|
|
901
899
|
const token = _nextModelLoadToken('runtime', inputId);
|
|
902
900
|
if (!runtimeName) {
|
|
@@ -906,42 +904,37 @@ async function loadModelsForRuntime(runtimeName, inputId, currentValue) {
|
|
|
906
904
|
}
|
|
907
905
|
let payload = { models: null };
|
|
908
906
|
try {
|
|
909
|
-
const
|
|
907
|
+
const suffix = forceRefresh ? '/models/refresh' : '/models';
|
|
908
|
+
const res = await fetch('/api/runtimes/' + encodeURIComponent(runtimeName) + suffix, forceRefresh ? { method: 'POST' } : undefined);
|
|
910
909
|
if (res.ok) payload = await res.json();
|
|
911
910
|
} catch { /* fall through to free-text */ }
|
|
912
911
|
|
|
913
912
|
if (!_isCurrentModelLoad('runtime', inputId, token)) return;
|
|
914
913
|
const models = Array.isArray(payload.models) ? payload.models : null;
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: currentValue; inputId is an internal fixed DOM id)
|
|
919
|
-
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:var(--text-md)">';
|
|
920
|
-
return;
|
|
921
|
-
}
|
|
922
|
-
// Dropdown. The first option submits empty string → "Default (CLI chooses)".
|
|
923
|
-
let opts = '<option value=""' + (!currentValue ? ' selected' : '') + '>Default (CLI chooses)</option>';
|
|
924
|
-
for (const m of models) {
|
|
914
|
+
const listId = inputId + '-options';
|
|
915
|
+
let opts = '';
|
|
916
|
+
for (const m of (models || [])) {
|
|
925
917
|
const id = m.id || m.name || '';
|
|
926
918
|
if (!id) continue;
|
|
927
|
-
const label = m.name && m.name !== id ?
|
|
928
|
-
opts += '<option value="' + escHtml(id) + '"' + (
|
|
919
|
+
const label = m.name && m.name !== id ? m.name : '';
|
|
920
|
+
opts += '<option value="' + escHtml(id) + '"' + (label ? ' label="' + escHtml(label) + '"' : '') + '></option>';
|
|
929
921
|
}
|
|
930
|
-
|
|
931
|
-
//
|
|
932
|
-
|
|
933
|
-
|
|
922
|
+
const title = forceRefresh ? 'Model list refreshed' : 'Refresh models from the selected CLI';
|
|
923
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml()
|
|
924
|
+
wrap.innerHTML = '<div style="display:flex;gap:4px"><input id="' + inputId + '" list="' + listId + '" value="' + escHtml(currentValue || '') + '" placeholder="Default (CLI chooses)" autocomplete="off" style="min-width:0;flex:1;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:var(--text-md)"><button type="button" data-model-refresh title="' + escHtml(title) + '" aria-label="Refresh model list" style="padding:3px 8px;background:var(--surface2);border:1px solid var(--border);border-radius:4px;color:var(--muted);cursor:pointer">↻</button></div><datalist id="' + listId + '">' + opts + '</datalist>';
|
|
925
|
+
const refresh = wrap.querySelector('[data-model-refresh]');
|
|
926
|
+
if (refresh) {
|
|
927
|
+
refresh.addEventListener('click', async function() {
|
|
928
|
+
const liveValue = document.getElementById(inputId)?.value || '';
|
|
929
|
+
refresh.disabled = true;
|
|
930
|
+
await loadModelsForRuntime(runtimeName, inputId, liveValue, true);
|
|
931
|
+
});
|
|
934
932
|
}
|
|
935
|
-
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: model id, model label, currentValue; inputId is an internal fixed DOM id)
|
|
936
|
-
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:var(--text-md)">' + opts + '</select>';
|
|
937
933
|
}
|
|
938
934
|
|
|
939
935
|
/**
|
|
940
|
-
* Per-agent model hydrator.
|
|
941
|
-
*
|
|
942
|
-
* given runtime. Output element keeps `data-agent` + `data-field="model"` so
|
|
943
|
-
* the existing save flow picks it up unchanged. Free-text input fallback
|
|
944
|
-
* when the runtime returns no model list (Claude / discovery disabled).
|
|
936
|
+
* Per-agent model hydrator. The catalog is a datalist rather than a closed
|
|
937
|
+
* select so newly released and custom IDs stay available.
|
|
945
938
|
*/
|
|
946
939
|
async function loadModelsForAgent(agentId, runtimeName, currentValue) {
|
|
947
940
|
const cell = document.querySelector('[data-runtime-model="' + agentId + '"]');
|
package/dashboard.js
CHANGED
|
@@ -4502,6 +4502,7 @@ async function _preflightModelCheck({ runtime: cliOverride, model: modelOverride
|
|
|
4502
4502
|
const resolvedModel = llm._resolveModelForRuntime(adapter, { model: modelOverride, engineConfig });
|
|
4503
4503
|
if (!resolvedModel) return null;
|
|
4504
4504
|
if (!adapter.capabilities || adapter.capabilities.modelDiscovery !== true) return null;
|
|
4505
|
+
if (adapter.capabilities.strictModelCatalog === false) return null;
|
|
4505
4506
|
|
|
4506
4507
|
let list;
|
|
4507
4508
|
try {
|
|
@@ -10572,8 +10573,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
10572
10573
|
});
|
|
10573
10574
|
writeCcEvent(envelope);
|
|
10574
10575
|
liveState.donePayload = envelope;
|
|
10575
|
-
_ccStreamEnded = true;
|
|
10576
10576
|
if (liveState.endResponse) liveState.endResponse();
|
|
10577
|
+
_ccStreamEnded = true;
|
|
10577
10578
|
_scheduleCcLiveCleanup(tabId);
|
|
10578
10579
|
_logCcStreamEnd(_ccTelemetry, 'error-preflight-model-unavailable', { runtime: preflightFailure.runtime });
|
|
10579
10580
|
return;
|
|
@@ -10621,8 +10622,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
10621
10622
|
if (initial.missingRuntime) return initial;
|
|
10622
10623
|
|
|
10623
10624
|
// Handle failure — non-zero exit with text = max_turns or partial success, still usable
|
|
10624
|
-
if (!initial.text && wasResume && initial.code !== 0
|
|
10625
|
-
// Resume failed (stale/expired session) — auto-retry as fresh
|
|
10625
|
+
if (!initial.text && wasResume && initial.code !== 0) {
|
|
10626
|
+
// Resume failed (stale/expired session) — auto-retry as fresh. A
|
|
10627
|
+
// briefly disconnected client can reattach to the live turn.
|
|
10626
10628
|
console.log(`[CC-stream] Resume failed (code=${initial.code}) — retrying fresh`);
|
|
10627
10629
|
const freshPreamble = buildCCStatePreamble();
|
|
10628
10630
|
const freshCarryover = _buildTranscriptCarryover(body.transcript, { currentMessage: body.message });
|
|
@@ -10672,11 +10674,6 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
10672
10674
|
// treating an empty `text` as a failure. A genuinely empty turn has
|
|
10673
10675
|
// both text and raw empty.
|
|
10674
10676
|
if ((!result.text && !result.raw) || result.error) {
|
|
10675
|
-
if (req.destroyed) {
|
|
10676
|
-
_ccStreamEnded = true;
|
|
10677
|
-
_logCcStreamEnd(_ccTelemetry, 'llm-empty-client-gone', { code: result.code });
|
|
10678
|
-
return;
|
|
10679
|
-
}
|
|
10680
10677
|
// W-mpmwxni2000c25c7-b — surface the typed error envelope as a
|
|
10681
10678
|
// distinct SSE `event: error` frame so the client renders a real
|
|
10682
10679
|
// error UI (with a retry hint derived from `retriable`) instead of
|
|
@@ -11316,20 +11313,18 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
11316
11313
|
knownForResolved = new Set(list.models.map(m => m.id || m.name).filter(Boolean));
|
|
11317
11314
|
}
|
|
11318
11315
|
} catch { /* unknown runtime */ }
|
|
11319
|
-
if (knownForResolved &&
|
|
11320
|
-
|
|
11321
|
-
|
|
11322
|
-
|
|
11323
|
-
|
|
11324
|
-
|
|
11325
|
-
|
|
11326
|
-
|
|
11327
|
-
|
|
11328
|
-
|
|
11329
|
-
|
|
11330
|
-
|
|
11331
|
-
} catch { /* skip */ }
|
|
11332
|
-
}
|
|
11316
|
+
if (knownForResolved && knownForResolved.has(runtimeModelStr)) return null;
|
|
11317
|
+
// Catalogs lag staged releases and local/custom providers. Unknown is
|
|
11318
|
+
// therefore allowed, but a model positively published by a different
|
|
11319
|
+
// runtime remains an actionable incompatible-combination error.
|
|
11320
|
+
for (const rt of _engineRuntimes.listRuntimes()) {
|
|
11321
|
+
if (rt === resolvedRuntime) continue;
|
|
11322
|
+
try {
|
|
11323
|
+
const otherList = await _engineModelDiscovery.getRuntimeModels(rt, { config });
|
|
11324
|
+
if (Array.isArray(otherList?.models) && otherList.models.some(m => (m.id || m.name) === modelStr)) {
|
|
11325
|
+
return `belongs to runtime "${rt}" but resolved runtime is "${resolvedRuntime}" — incompatible combination`;
|
|
11326
|
+
}
|
|
11327
|
+
} catch { /* skip */ }
|
|
11333
11328
|
}
|
|
11334
11329
|
return null;
|
|
11335
11330
|
}
|
|
@@ -11486,15 +11481,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
11486
11481
|
const resolvedCli = config.agents[id].cli || config.engine.defaultCli || 'copilot';
|
|
11487
11482
|
const runtimeModelStr = _resolveModelForRuntime(candidate, resolvedCli);
|
|
11488
11483
|
const knownModels = await _modelsFor(resolvedCli);
|
|
11489
|
-
//
|
|
11490
|
-
//
|
|
11491
|
-
//
|
|
11492
|
-
// model belongs to a DIFFERENT runtime's list — that's how
|
|
11493
|
-
// we catch claude+gpt-5.5 (gpt-5.5 is in Copilot's list).
|
|
11484
|
+
// Published membership is a positive signal, not a closed enum:
|
|
11485
|
+
// staged releases/custom providers can legitimately be absent.
|
|
11486
|
+
// Still reject IDs positively owned by a different runtime.
|
|
11494
11487
|
let rejection = null;
|
|
11495
|
-
if (knownModels
|
|
11496
|
-
rejection = `not a valid model for runtime "${resolvedCli}" (known: ${[...knownModels].slice(0, 4).join(', ')}${knownModels.size > 4 ? '…' : ''})`;
|
|
11497
|
-
} else if (!knownModels) {
|
|
11488
|
+
if (!knownModels || !knownModels.has(runtimeModelStr)) {
|
|
11498
11489
|
const owner = await _ownerOfModel(candidate);
|
|
11499
11490
|
if (owner && owner !== resolvedCli) {
|
|
11500
11491
|
rejection = `belongs to runtime "${owner}" but agent uses "${resolvedCli}" — incompatible combination`;
|
package/engine/llm.js
CHANGED
|
@@ -591,8 +591,9 @@ function _createStreamAccumulator({
|
|
|
591
591
|
if (!id || !onToolUpdate) return;
|
|
592
592
|
onToolUpdate(id, status);
|
|
593
593
|
},
|
|
594
|
-
toolUseAlreadySeen(name, input) {
|
|
594
|
+
toolUseAlreadySeen(name, input, id = null) {
|
|
595
595
|
if (!name) return false;
|
|
596
|
+
if (id) return toolUses.some(t => t.id === id);
|
|
596
597
|
const stringified = JSON.stringify(input || {});
|
|
597
598
|
return toolUses.some(t => t.name === name && JSON.stringify(t.input) === stringified);
|
|
598
599
|
},
|
|
@@ -99,6 +99,13 @@ async function getRuntimeModels(runtimeName, { force = false, ttlMs = MODEL_CACH
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
const cachePath = adapter.modelsCache || null;
|
|
102
|
+
// Runtime catalogs are versioned with the CLI that produced them. Codex in
|
|
103
|
+
// particular ships new bundled model IDs with CLI releases, so a one-hour
|
|
104
|
+
// time-only cache otherwise hides new models immediately after an upgrade.
|
|
105
|
+
let modelCacheKey = null;
|
|
106
|
+
if (typeof adapter.getModelCacheKey === 'function') {
|
|
107
|
+
try { modelCacheKey = await adapter.getModelCacheKey(); } catch { /* optional hint */ }
|
|
108
|
+
}
|
|
102
109
|
|
|
103
110
|
// Cache hit path — only on non-forced reads. We accept any cached payload
|
|
104
111
|
// whose `cachedAt` parses to a valid timestamp within the TTL window;
|
|
@@ -110,7 +117,8 @@ async function getRuntimeModels(runtimeName, { force = false, ttlMs = MODEL_CACH
|
|
|
110
117
|
const ts = Date.parse(cached.cachedAt);
|
|
111
118
|
if (Number.isFinite(ts)) {
|
|
112
119
|
const age = Date.now() - ts;
|
|
113
|
-
|
|
120
|
+
const versionMatches = !modelCacheKey || cached.modelCacheKey === modelCacheKey;
|
|
121
|
+
if (age >= 0 && age < ttlMs && versionMatches) {
|
|
114
122
|
const models = Array.isArray(cached.models) ? cached.models : null;
|
|
115
123
|
return { runtime: runtimeName, models, cachedAt: cached.cachedAt };
|
|
116
124
|
}
|
|
@@ -134,7 +142,12 @@ async function getRuntimeModels(runtimeName, { force = false, ttlMs = MODEL_CACH
|
|
|
134
142
|
|
|
135
143
|
const cachedAt = new Date().toISOString();
|
|
136
144
|
if (cachePath) {
|
|
137
|
-
_writeCacheFile(cachePath, {
|
|
145
|
+
_writeCacheFile(cachePath, {
|
|
146
|
+
runtime: runtimeName,
|
|
147
|
+
models,
|
|
148
|
+
cachedAt,
|
|
149
|
+
...(modelCacheKey ? { modelCacheKey } : {}),
|
|
150
|
+
});
|
|
138
151
|
}
|
|
139
152
|
return { runtime: runtimeName, models, cachedAt };
|
|
140
153
|
}
|
package/engine/runtimes/codex.js
CHANGED
|
@@ -320,6 +320,17 @@ function _normalizeConfigOverrides(configOverrides) {
|
|
|
320
320
|
return [];
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
const _MIME_EXT = {
|
|
324
|
+
'image/png': 'png',
|
|
325
|
+
'image/jpeg': 'jpg',
|
|
326
|
+
'image/gif': 'gif',
|
|
327
|
+
'image/webp': 'webp',
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
function _mimeToExt(mimeType) {
|
|
331
|
+
return _MIME_EXT[String(mimeType || '').toLowerCase()] || 'bin';
|
|
332
|
+
}
|
|
333
|
+
|
|
323
334
|
function buildArgs(opts = {}) {
|
|
324
335
|
const {
|
|
325
336
|
model,
|
|
@@ -329,11 +340,13 @@ function buildArgs(opts = {}) {
|
|
|
329
340
|
sandbox = 'workspace-write',
|
|
330
341
|
skipGitRepoCheck = true,
|
|
331
342
|
configOverrides,
|
|
343
|
+
images,
|
|
344
|
+
tmpDir,
|
|
332
345
|
} = opts;
|
|
333
346
|
|
|
334
347
|
const args = ['exec'];
|
|
335
|
-
|
|
336
|
-
|
|
348
|
+
// These are `exec` options, not `exec resume` options. Codex's resume
|
|
349
|
+
// parser rejects flags such as --color and --sandbox after the subcommand.
|
|
337
350
|
args.push('--json', '--color', 'never');
|
|
338
351
|
if (model) args.push('--model', String(model));
|
|
339
352
|
if (sandbox) args.push('--sandbox', String(sandbox));
|
|
@@ -347,6 +360,20 @@ function buildArgs(opts = {}) {
|
|
|
347
360
|
args.push('--config', override);
|
|
348
361
|
}
|
|
349
362
|
|
|
363
|
+
if (sessionId) args.push('resume', String(sessionId));
|
|
364
|
+
|
|
365
|
+
// Codex accepts repeatable --image paths on fresh and resumed turns. The
|
|
366
|
+
// call-scoped tmpDir is removed by engine/llm.js after the process exits.
|
|
367
|
+
if (Array.isArray(images) && images.length && tmpDir) {
|
|
368
|
+
for (let i = 0; i < images.length; i++) {
|
|
369
|
+
const img = images[i];
|
|
370
|
+
if (!img || !img.dataBase64) continue;
|
|
371
|
+
const filePath = path.join(tmpDir, `img-${i}.${_mimeToExt(img.mimeType)}`);
|
|
372
|
+
fs.writeFileSync(filePath, Buffer.from(img.dataBase64, 'base64'));
|
|
373
|
+
args.push('--image', filePath);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
350
377
|
args.push('-');
|
|
351
378
|
return args;
|
|
352
379
|
}
|
|
@@ -544,6 +571,21 @@ function _extractToolUse(obj) {
|
|
|
544
571
|
const itemType = String(item?.type || '');
|
|
545
572
|
if (!/tool|function|command|web_search/i.test(type) && !/tool|command|web_search/i.test(itemType)) return null;
|
|
546
573
|
const data = item || obj.data || obj;
|
|
574
|
+
const id = _pickString(data.id, obj?.item_id, obj?.itemId, obj?.data?.item_id);
|
|
575
|
+
const rawStatus = String(data.status || obj?.status || '').toLowerCase();
|
|
576
|
+
const status = /fail|error|cancel/.test(rawStatus) ? 'failed'
|
|
577
|
+
: /complete|success/.test(rawStatus) ? 'completed'
|
|
578
|
+
: null;
|
|
579
|
+
|
|
580
|
+
if (/command_execution/i.test(itemType)) {
|
|
581
|
+
const command = _pickString(data.command, data.input);
|
|
582
|
+
if (!command) return null;
|
|
583
|
+
return { name: 'Bash', input: { command }, id: id || null, status };
|
|
584
|
+
}
|
|
585
|
+
if (/web_search/i.test(itemType)) {
|
|
586
|
+
const query = _pickString(data.query, data.input, data.arguments);
|
|
587
|
+
return { name: 'WebSearch', input: query ? { query } : {}, id: id || null, status };
|
|
588
|
+
}
|
|
547
589
|
const name = _pickString(
|
|
548
590
|
data.tool_name,
|
|
549
591
|
data.toolName,
|
|
@@ -555,7 +597,7 @@ function _extractToolUse(obj) {
|
|
|
555
597
|
);
|
|
556
598
|
if (!name) return null;
|
|
557
599
|
const input = data.arguments || data.args || data.input || data.function?.arguments || {};
|
|
558
|
-
return { name, input };
|
|
600
|
+
return { name, input, id: id || null, status };
|
|
559
601
|
}
|
|
560
602
|
|
|
561
603
|
function _usageFrom(obj) {
|
|
@@ -675,6 +717,17 @@ function parseError(rawOutput) {
|
|
|
675
717
|
if (!text) return { message: '', code: null, retriable: true };
|
|
676
718
|
const lower = text.toLowerCase();
|
|
677
719
|
|
|
720
|
+
if (/requires a newer version of codex|upgrade to the latest (?:app or )?cli|codex (?:cli )?is (?:too old|out of date)/i.test(text)) {
|
|
721
|
+
const name = _extractInvalidModelName(text)
|
|
722
|
+
|| (text.match(/['"`]([A-Za-z0-9._\/-]+)['"`]/)?.[1])
|
|
723
|
+
|| 'configured model';
|
|
724
|
+
return {
|
|
725
|
+
message: `The installed Codex CLI is too old for model "${name}". Upgrade Codex, refresh the model list, and try again.`,
|
|
726
|
+
code: 'config-error',
|
|
727
|
+
retriable: false,
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
|
|
678
731
|
const hasAuth = /not authenticated|login required|please.*log.*in|api key.*invalid|invalid api key|\bunauthorized\b|\b401\b|\b403\b/i.test(text);
|
|
679
732
|
if (hasAuth) {
|
|
680
733
|
return { message: 'Codex authentication failed - run `codex login` or configure an OpenAI API key, then try again.', code: 'auth-failure', retriable: false };
|
|
@@ -773,6 +826,20 @@ async function listModels({ env = process.env, timeoutMs = 10000 } = {}) {
|
|
|
773
826
|
}
|
|
774
827
|
}
|
|
775
828
|
|
|
829
|
+
function getModelCacheKey({ env = process.env, timeoutMs = 10000 } = {}) {
|
|
830
|
+
const resolved = resolveBinary({ env });
|
|
831
|
+
if (!resolved) return null;
|
|
832
|
+
try {
|
|
833
|
+
return _execFileCapture(resolved.bin, [...(resolved.leadingArgs || []), '--version'], {
|
|
834
|
+
env,
|
|
835
|
+
timeoutMs,
|
|
836
|
+
native: resolved.native,
|
|
837
|
+
}).trim() || null;
|
|
838
|
+
} catch {
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
776
843
|
function createStreamConsumer(ctx) {
|
|
777
844
|
let deltaText = '';
|
|
778
845
|
let terminalText = '';
|
|
@@ -789,7 +856,11 @@ function createStreamConsumer(ctx) {
|
|
|
789
856
|
if (/reasoning|thinking/i.test(type)) ctx.notifyThinking();
|
|
790
857
|
|
|
791
858
|
const toolUse = _extractToolUse(obj);
|
|
792
|
-
if (toolUse)
|
|
859
|
+
if (toolUse) {
|
|
860
|
+
const seen = ctx.toolUseAlreadySeen(toolUse.name, toolUse.input, toolUse.id);
|
|
861
|
+
if (!seen) ctx.pushToolUse(toolUse.name, toolUse.input, toolUse.id);
|
|
862
|
+
if (toolUse.id && toolUse.status) ctx.updateToolUse(toolUse.id, toolUse.status);
|
|
863
|
+
}
|
|
793
864
|
|
|
794
865
|
const delta = _extractDelta(obj);
|
|
795
866
|
if (delta) {
|
|
@@ -823,6 +894,9 @@ const capabilities = {
|
|
|
823
894
|
costTracking: false,
|
|
824
895
|
modelShorthands: false,
|
|
825
896
|
modelDiscovery: true,
|
|
897
|
+
// Bundled catalogs can lag staged/custom model availability. Let Codex
|
|
898
|
+
// validate unknown IDs so it can return its precise version/provider error.
|
|
899
|
+
strictModelCatalog: false,
|
|
826
900
|
promptViaArg: false,
|
|
827
901
|
budgetCap: false,
|
|
828
902
|
bareMode: false,
|
|
@@ -830,9 +904,7 @@ const capabilities = {
|
|
|
830
904
|
sessionPersistenceControl: false,
|
|
831
905
|
resumePromptCarryover: true,
|
|
832
906
|
streamConsumer: true,
|
|
833
|
-
|
|
834
|
-
// P-3f8a1c92). Engine code gates on this flag, never on runtime.name.
|
|
835
|
-
imageInput: false,
|
|
907
|
+
imageInput: true,
|
|
836
908
|
};
|
|
837
909
|
|
|
838
910
|
const INSTALL_HINT = 'install Codex CLI with `npm install -g @openai/codex` or `brew install --cask codex`, then run `codex login`.';
|
|
@@ -844,6 +916,7 @@ module.exports = {
|
|
|
844
916
|
resolveBinary,
|
|
845
917
|
capsFile: CAPS_FILE,
|
|
846
918
|
listModels,
|
|
919
|
+
getModelCacheKey,
|
|
847
920
|
modelsCache: MODELS_CACHE,
|
|
848
921
|
spawnScript: path.join(ENGINE_DIR, 'spawn-agent.js'),
|
|
849
922
|
installHint: INSTALL_HINT,
|
|
@@ -878,5 +951,6 @@ module.exports = {
|
|
|
878
951
|
_extractModelsFromCatalog,
|
|
879
952
|
_readCatalogIds,
|
|
880
953
|
_extractInvalidModelName,
|
|
954
|
+
_extractToolUse,
|
|
881
955
|
CAPS_SCHEMA_VERSION,
|
|
882
956
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2377",
|
|
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"
|