@yemi33/minions 0.1.2045 → 0.1.2047
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/README.md +2 -2
- package/dashboard/js/fre.js +3 -2
- package/dashboard/js/render-prs.js +82 -2
- package/dashboard/js/settings.js +5 -5
- package/dashboard/styles.css +11 -0
- package/dashboard.js +376 -135
- package/docs/copilot-cli-schema.md +2 -1
- package/docs/runtime-adapters.md +9 -4
- package/engine/cc-worker-pool.js +87 -11
- package/engine/llm.js +148 -2
- package/engine/preflight.js +5 -5
- package/engine/queries.js +75 -35
- package/engine/runtimes/claude.js +41 -0
- package/engine/runtimes/copilot.js +97 -3
- package/engine/shared.js +4 -3
- package/package.json +1 -1
|
@@ -522,6 +522,27 @@ function parseStreamChunk(line) {
|
|
|
522
522
|
|
|
523
523
|
// ── Error Normalization ──────────────────────────────────────────────────────
|
|
524
524
|
|
|
525
|
+
// Pull a model id out of an Anthropic-style invalid-model error string. Claude
|
|
526
|
+
// CLI surfaces these as either plain stderr ("Unknown model: claude-x") or as
|
|
527
|
+
// the API's structured error body ({"type":"error","error":{"message":
|
|
528
|
+
// "model: claude-x: not a valid model"}}). Returns null when no obvious model
|
|
529
|
+
// token is present — parseError falls back to "unknown" in that case.
|
|
530
|
+
function _extractInvalidModelName(text) {
|
|
531
|
+
if (!text) return null;
|
|
532
|
+
const patterns = [
|
|
533
|
+
/(?:unknown|invalid)\s+model(?:\s+id)?\s*[:=]?\s*['"`]?([A-Za-z0-9._\/-]+)['"`]?/i,
|
|
534
|
+
/model\s+['"`]([^'"`]+)['"`]\s+(?:not\s+found|is\s+invalid|is\s+unknown|invalid)/i,
|
|
535
|
+
/model\s*[:=]\s*['"`]?([A-Za-z0-9._\/-]+)['"`]?\s*[:,]\s*(?:not\s+a\s+valid\s+model|not\s+found|invalid)/i,
|
|
536
|
+
/model\s+(?:not\s+found|invalid|unknown)\s*[:=]?\s*['"`]?([A-Za-z0-9._\/-]+)['"`]?/i,
|
|
537
|
+
/"model"\s*:\s*"([^"]+)"/i,
|
|
538
|
+
];
|
|
539
|
+
for (const re of patterns) {
|
|
540
|
+
const m = text.match(re);
|
|
541
|
+
if (m && m[1]) return m[1];
|
|
542
|
+
}
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
|
|
525
546
|
/**
|
|
526
547
|
* Inspect raw agent output (stdout/stderr concatenated by the caller) and map
|
|
527
548
|
* common Claude error patterns onto a normalized shape:
|
|
@@ -531,6 +552,11 @@ function parseStreamChunk(line) {
|
|
|
531
552
|
* - 'auth-failure' — invalid API key / credit-card / org-blocked
|
|
532
553
|
* - 'context-limit' — context window exhausted
|
|
533
554
|
* - 'budget-exceeded' — `--max-budget-usd` ceiling hit
|
|
555
|
+
* - 'model-unavailable' — model not available. Two flavors distinguished by
|
|
556
|
+
* `retriable`: retriable=true for transient upstream
|
|
557
|
+
* overload (503/overloaded_error — engine retries
|
|
558
|
+
* with `--fallback-model`); retriable=false for an
|
|
559
|
+
* invalid/typo'd model id (user must fix config).
|
|
534
560
|
* - 'crash' — CLI crashed (segfault, panic, "Internal error")
|
|
535
561
|
* - null — no recognised pattern
|
|
536
562
|
*
|
|
@@ -553,6 +579,20 @@ function parseError(rawOutput) {
|
|
|
553
579
|
if (/budget.*exceed|max.budget.usd.*reach|cost.*limit.*exceed/i.test(lower)) {
|
|
554
580
|
return { message: 'Claude budget cap exceeded — check your Claude account spending limit.', code: 'budget-exceeded', retriable: false };
|
|
555
581
|
}
|
|
582
|
+
// W-mpmwxni2000c25c7-a — Configured model isn't a valid Anthropic id (typo,
|
|
583
|
+
// deprecated id, invalid_request_error on `model:` field). Claude has no
|
|
584
|
+
// listModels() catalog (capabilities.modelDiscovery === false), so the
|
|
585
|
+
// message points the operator at Settings instead of enumerating ids.
|
|
586
|
+
// Match BEFORE the overload branch — the overload regex matches
|
|
587
|
+
// `model.*unavailable` and would otherwise swallow "model X is invalid".
|
|
588
|
+
if (/unknown\s+model|model\s+not\s+found|invalid\s+model|model_not_found|not\s+a\s+valid\s+model|400[^a-z]+(?:bad\s+request|invalid|model)/i.test(text)) {
|
|
589
|
+
const name = _extractInvalidModelName(text) || 'unknown';
|
|
590
|
+
return {
|
|
591
|
+
message: `Model "${name}" not available for runtime claude. Configure a valid model in Settings → Engine.`,
|
|
592
|
+
code: 'model-unavailable',
|
|
593
|
+
retriable: false,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
556
596
|
// W-mpg6isvy000xca4d — Anthropic overload / 503 / service-unavailable. Claude's
|
|
557
597
|
// own `--fallback-model` only fires on 429 (rate-limit); these failure modes
|
|
558
598
|
// hang the agent until the 5h timeout. Classify as MODEL_UNAVAILABLE so the
|
|
@@ -828,5 +868,6 @@ module.exports = {
|
|
|
828
868
|
permissionBypassFlags: PERMISSION_BYPASS_FLAGS,
|
|
829
869
|
// Exposed for unit tests — never imported by engine code
|
|
830
870
|
_CLAUDE_SHORTHANDS,
|
|
871
|
+
_extractInvalidModelName,
|
|
831
872
|
THINKING_BLOCK_TYPES,
|
|
832
873
|
};
|
|
@@ -785,6 +785,58 @@ function parseStreamChunk(line) {
|
|
|
785
785
|
|
|
786
786
|
// ── Error Normalization ─────────────────────────────────────────────────────
|
|
787
787
|
|
|
788
|
+
// In-memory model-discovery cache used by parseError's invalid-model branch
|
|
789
|
+
// (W-mpmwxni2000c25c7-a). parseError is synchronous — to enrich the error
|
|
790
|
+
// message with the live model catalog without adding a per-error HTTP round
|
|
791
|
+
// trip, the engine pre-warms this cache via `_warmModelCache()` (e.g. during
|
|
792
|
+
// preflight or the first listModels() call from the dashboard). Concurrent
|
|
793
|
+
// callers share the in-flight promise so the API is only hit once even when
|
|
794
|
+
// multiple agents fail simultaneously. `_modelDiscoveryResults` stores the
|
|
795
|
+
// resolved catalog (or `[]` on failure); parseError reads it synchronously.
|
|
796
|
+
let _modelDiscoveryResults = null;
|
|
797
|
+
let _modelDiscoveryPromise = null;
|
|
798
|
+
|
|
799
|
+
async function _warmModelCache(opts = {}) {
|
|
800
|
+
if (_modelDiscoveryPromise) return _modelDiscoveryPromise;
|
|
801
|
+
if (Array.isArray(_modelDiscoveryResults)) return _modelDiscoveryResults;
|
|
802
|
+
_modelDiscoveryPromise = listModels(opts).then(
|
|
803
|
+
(models) => { _modelDiscoveryResults = Array.isArray(models) ? models : []; return _modelDiscoveryResults; },
|
|
804
|
+
() => { _modelDiscoveryResults = []; return _modelDiscoveryResults; },
|
|
805
|
+
);
|
|
806
|
+
try {
|
|
807
|
+
return await _modelDiscoveryPromise;
|
|
808
|
+
} finally {
|
|
809
|
+
_modelDiscoveryPromise = null;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function _resetModelCache({ models = null } = {}) {
|
|
814
|
+
_modelDiscoveryResults = Array.isArray(models) ? models : (models === null ? null : []);
|
|
815
|
+
_modelDiscoveryPromise = null;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// Pull a model id out of a Copilot/Anthropic-style invalid-model error string.
|
|
819
|
+
// Handles the shapes observed in the wild:
|
|
820
|
+
// "Unknown model: banana"
|
|
821
|
+
// "model not found: gpt-5.4"
|
|
822
|
+
// "model 'gpt-x' is invalid"
|
|
823
|
+
// "Invalid model id: claude-sonnet"
|
|
824
|
+
// {"error":"model_not_found","model":"gpt-x"} / {"model":"gpt-x"}
|
|
825
|
+
function _extractInvalidModelName(text) {
|
|
826
|
+
if (!text) return null;
|
|
827
|
+
const patterns = [
|
|
828
|
+
/(?:unknown|invalid)\s+model(?:\s+id)?\s*[:=]?\s*['"`]?([A-Za-z0-9._\/-]+)['"`]?/i,
|
|
829
|
+
/model\s+['"`]([^'"`]+)['"`]\s+(?:not\s+found|is\s+invalid|is\s+unknown|invalid)/i,
|
|
830
|
+
/model\s+(?:not\s+found|invalid|unknown)\s*[:=]?\s*['"`]?([A-Za-z0-9._\/-]+)['"`]?/i,
|
|
831
|
+
/"model"\s*:\s*"([^"]+)"/i,
|
|
832
|
+
];
|
|
833
|
+
for (const re of patterns) {
|
|
834
|
+
const m = text.match(re);
|
|
835
|
+
if (m && m[1]) return m[1];
|
|
836
|
+
}
|
|
837
|
+
return null;
|
|
838
|
+
}
|
|
839
|
+
|
|
788
840
|
function _collectErrorSignal(rawOutput) {
|
|
789
841
|
const text = rawOutput == null ? '' : String(rawOutput);
|
|
790
842
|
if (!text) return '';
|
|
@@ -835,6 +887,27 @@ function _collectErrorSignal(rawOutput) {
|
|
|
835
887
|
return sawJsonLine ? '' : text;
|
|
836
888
|
}
|
|
837
889
|
|
|
890
|
+
function _readModelIdsFromDiskCache() {
|
|
891
|
+
try {
|
|
892
|
+
const text = fs.readFileSync(MODELS_CACHE, 'utf8');
|
|
893
|
+
const obj = JSON.parse(text);
|
|
894
|
+
if (!obj || !Array.isArray(obj.models)) return null;
|
|
895
|
+
const ids = obj.models.map(m => m && m.id ? String(m.id) : null).filter(Boolean);
|
|
896
|
+
return ids.length > 0 ? ids : null;
|
|
897
|
+
} catch { return null; }
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function _resolveCachedModelIds() {
|
|
901
|
+
// In-memory cache (seeded by `_warmModelCache()` or test `_resetModelCache`)
|
|
902
|
+
// wins over disk so unit tests stay hermetic. Production code that hasn't
|
|
903
|
+
// warmed the in-memory cache still gets the catalog via the disk file that
|
|
904
|
+
// `engine/model-discovery.js#getRuntimeModels` populates during preflight.
|
|
905
|
+
if (Array.isArray(_modelDiscoveryResults) && _modelDiscoveryResults.length > 0) {
|
|
906
|
+
return _modelDiscoveryResults.map(m => m && m.id ? String(m.id) : null).filter(Boolean);
|
|
907
|
+
}
|
|
908
|
+
return _readModelIdsFromDiskCache();
|
|
909
|
+
}
|
|
910
|
+
|
|
838
911
|
function parseError(rawOutput) {
|
|
839
912
|
const text = _collectErrorSignal(rawOutput);
|
|
840
913
|
if (!text) return { message: '', code: null, retriable: true };
|
|
@@ -845,6 +918,24 @@ function parseError(rawOutput) {
|
|
|
845
918
|
if (hasExplicitAuthFailure || hasAuthStatusCode) {
|
|
846
919
|
return { message: text, code: 'auth-failure', retriable: false };
|
|
847
920
|
}
|
|
921
|
+
// W-mpmwxni2000c25c7-a — Configured model isn't in the Copilot catalog
|
|
922
|
+
// (typo, deprecated id, 400-style invalid-model response). Classify as
|
|
923
|
+
// MODEL_UNAVAILABLE with retriable: false so the engine surfaces an
|
|
924
|
+
// actionable error instead of looping the dispatch. Must match BEFORE the
|
|
925
|
+
// overload branch — the overload regex catches `model.*unavailable`,
|
|
926
|
+
// which would otherwise swallow legitimate "model not found" responses.
|
|
927
|
+
if (/unknown\s+model|model\s+not\s+found|invalid\s+model|model_not_found|400[^a-z]+(?:bad\s+request|invalid|model)|model.*\b400\b/i.test(text)) {
|
|
928
|
+
const name = _extractInvalidModelName(text) || 'unknown';
|
|
929
|
+
const ids = _resolveCachedModelIds();
|
|
930
|
+
const tail = (ids && ids.length > 0)
|
|
931
|
+
? `Available models: ${ids.join(', ')}`
|
|
932
|
+
: 'Configure a valid model in Settings → Engine.';
|
|
933
|
+
return {
|
|
934
|
+
message: `Model "${name}" not available for runtime copilot. ${tail}`,
|
|
935
|
+
code: 'model-unavailable',
|
|
936
|
+
retriable: false,
|
|
937
|
+
};
|
|
938
|
+
}
|
|
848
939
|
// W-mpg6isvy000xca4d — Copilot has no --fallback-model flag; classify
|
|
849
940
|
// overloaded / 503 / service_unavailable as MODEL_UNAVAILABLE so the engine
|
|
850
941
|
// retry can OVERRIDE --model with engine.copilotFallbackModel. Match before
|
|
@@ -856,9 +947,6 @@ function parseError(rawOutput) {
|
|
|
856
947
|
if (/rate limit|too many requests|\b429\b/i.test(text)) {
|
|
857
948
|
return { message: text, code: 'rate-limit', retriable: true };
|
|
858
949
|
}
|
|
859
|
-
if (/unknown model|model not found|model.*invalid|invalid model/i.test(text)) {
|
|
860
|
-
return { message: text, code: 'unknown-model', retriable: false };
|
|
861
|
-
}
|
|
862
950
|
if (/budget.*exceed|premium.*limit.*reach|quota.*exceed/i.test(lower)) {
|
|
863
951
|
return { message: text, code: 'budget-exceeded', retriable: false };
|
|
864
952
|
}
|
|
@@ -1198,6 +1286,12 @@ module.exports = {
|
|
|
1198
1286
|
_pickStandaloneCopilotFromOutput,
|
|
1199
1287
|
_resolveNpmCopilotJsEntry,
|
|
1200
1288
|
_isCachedBinUsable,
|
|
1289
|
+
// W-mpmwxni2000c25c7-a — invalid-model error-path helpers. `_warmModelCache`
|
|
1290
|
+
// populates the in-memory model catalog so parseError can enrich its
|
|
1291
|
+
// "Model X not available" message without a per-error HTTP round trip.
|
|
1292
|
+
_warmModelCache,
|
|
1293
|
+
_resetModelCache,
|
|
1294
|
+
_extractInvalidModelName,
|
|
1201
1295
|
CAPS_SCHEMA_VERSION,
|
|
1202
1296
|
KNOWN_EVENT_TYPES,
|
|
1203
1297
|
};
|
package/engine/shared.js
CHANGED
|
@@ -1856,7 +1856,7 @@ const ENGINE_DEFAULTS = {
|
|
|
1856
1856
|
// Engine code MUST go through the resolveAgent*/resolveCc* helpers below;
|
|
1857
1857
|
// never read these fields directly. New runtimes are added by registering
|
|
1858
1858
|
// an adapter in engine/runtimes/index.js — these defaults stay stable.
|
|
1859
|
-
defaultCli: '
|
|
1859
|
+
defaultCli: 'copilot', // fleet-wide CLI runtime (must be a key in engine/runtimes/index.js); flipped from 'claude' in W-mpmwxkk40007c995 — Copilot is now the primary runtime, Claude remains supported as an opt-in
|
|
1860
1860
|
defaultModel: undefined, // fleet-wide model; undefined = let the runtime adapter pick its own default
|
|
1861
1861
|
ccCli: undefined, // CC/doc-chat CLI override; undefined = inherit defaultCli (independent of agent path)
|
|
1862
1862
|
ccModel: undefined, // CC/doc-chat model override; undefined = inherit defaultModel
|
|
@@ -1879,6 +1879,7 @@ const ENGINE_DEFAULTS = {
|
|
|
1879
1879
|
removeWorktreeFailureTtlMs: 24 * 60 * 60 * 1000, // stale failed paths are forgotten after a day
|
|
1880
1880
|
removeWorktreeFailureMaxEntries: 1000, // bound failed-worktree retry suppression cache
|
|
1881
1881
|
ccMaxTurns: 50, // max tool-use turns per CC/doc-chat call before CLI stops (per response, not per session)
|
|
1882
|
+
ccTurnTimeoutMs: 300000, // W-mpmwxni2000c25c7-b: wall-clock cap per CC/doc-chat turn; on expiry the in-flight LLM call is aborted and the handler surfaces `{code:'cc-turn-timeout', retriable:true}` instead of hanging the UI
|
|
1882
1883
|
docSessionMaxEntries: 200, // cap doc-chat session map/disk store by least-recent activity (LRU; sessions are non-expiring otherwise)
|
|
1883
1884
|
ccLiveStreamMaxAgeMs: 30 * 60 * 1000, // hard cap reconnect buffers if abort/cleanup stalls
|
|
1884
1885
|
metricsFlushIntervalMs: 10000, // batch trackEngineUsage writes to metrics.json — flushed every 10s instead of per-call to cut lock contention and dashboard mtime churn
|
|
@@ -2084,7 +2085,7 @@ function _isMeaningful(v) {
|
|
|
2084
2085
|
* Resolve the CLI runtime for a per-agent spawn. Priority:
|
|
2085
2086
|
* 1. `agent.cli` — per-agent override
|
|
2086
2087
|
* 2. `engine.defaultCli` — fleet default
|
|
2087
|
-
* 3. `ENGINE_DEFAULTS.defaultCli` ('
|
|
2088
|
+
* 3. `ENGINE_DEFAULTS.defaultCli` ('copilot') — hardcoded fallback
|
|
2088
2089
|
*
|
|
2089
2090
|
* Does NOT fall through to `engine.ccCli`. CC and agents are independent paths.
|
|
2090
2091
|
*/
|
|
@@ -2098,7 +2099,7 @@ function resolveAgentCli(agent, engine) {
|
|
|
2098
2099
|
* Resolve the CLI runtime for the Command Center / doc-chat. Priority:
|
|
2099
2100
|
* 1. `engine.ccCli` — CC-only override
|
|
2100
2101
|
* 2. `engine.defaultCli` — fleet default
|
|
2101
|
-
* 3. `ENGINE_DEFAULTS.defaultCli` ('
|
|
2102
|
+
* 3. `ENGINE_DEFAULTS.defaultCli` ('copilot') — hardcoded fallback
|
|
2102
2103
|
*
|
|
2103
2104
|
* Does NOT inspect any agent overrides. CC has no notion of "which agent" —
|
|
2104
2105
|
* it's a fleet-wide singleton.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2047",
|
|
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"
|