@agentskit/harness 0.6.0 → 0.7.0

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/dist/cli.js CHANGED
@@ -953,15 +953,174 @@ var createDocBridgeContextProvider = ({ root, indexPath = ".doc-bridge/index.jso
953
953
  return { providerId: "doc-bridge", query, references, sourceHash: contentHash, snapshotHash: hashContextSnapshot({ providerId: "doc-bridge", query, references, sourceHash: contentHash }), resolvedAt: (/* @__PURE__ */ new Date()).toISOString(), assurance: "contract-tested", telemetry };
954
954
  }
955
955
  });
956
+ var executable = (path) => {
957
+ try {
958
+ return statSync(path).isFile();
959
+ } catch {
960
+ return false;
961
+ }
962
+ };
963
+ var findExecutable = (name2, env = process.env, platform = process.platform) => {
964
+ if (typeof name2 !== "string" || !name2.trim()) return null;
965
+ if (isAbsolute(name2) || name2.includes("/") || name2.includes("\\")) return existsSync(name2) && executable(name2) ? name2 : null;
966
+ const extensions = platform === "win32" ? (env["PATHEXT"] ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean) : [""];
967
+ for (const dir of (env["PATH"] ?? "").split(delimiter).filter(Boolean)) {
968
+ for (const extension of extensions) {
969
+ const candidate = join(dir, `${name2}${extension}`);
970
+ if (executable(candidate)) return candidate;
971
+ }
972
+ if (platform === "win32" && executable(join(dir, name2))) return join(dir, name2);
973
+ }
974
+ return null;
975
+ };
976
+ var parseJsonEnvelope = (stdout) => {
977
+ const trimmed = stdout.trim();
978
+ if (!trimmed) return null;
979
+ let parsed;
980
+ try {
981
+ parsed = JSON.parse(trimmed);
982
+ } catch {
983
+ return null;
984
+ }
985
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
986
+ const record3 = parsed;
987
+ if (typeof record3.ok !== "boolean") return null;
988
+ const error = typeof record3.error === "string" ? record3.error : typeof record3.error === "object" && record3.error !== null && typeof record3.error.message === "string" ? record3.error.message : void 0;
989
+ return { ok: record3.ok, result: record3.result, ...error === void 0 ? {} : { error } };
990
+ };
956
991
 
957
- // src/adapters/rag-context.ts
992
+ // src/adapters/providers.ts
958
993
  var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
994
+ var iso = (value) => typeof value === "number" && Number.isFinite(value) ? new Date(value).toISOString() : typeof value === "string" && !Number.isNaN(Date.parse(value)) ? new Date(value).toISOString() : null;
995
+ var parseUsageWindows = (entry) => {
996
+ if (!isRecord4(entry)) return [];
997
+ return Object.entries(entry).flatMap(([kind, value]) => {
998
+ if (!isRecord4(value) || typeof value["usedPercent"] !== "number") return [];
999
+ return [{ kind, usedPercent: value["usedPercent"], windowMinutes: typeof value["windowMinutes"] === "number" ? value["windowMinutes"] : null, resetsAt: iso(value["resetsAt"]) }];
1000
+ });
1001
+ };
1002
+ var parseProviderUsage = (accountList, usageKey, exhaustedPercent = 100) => {
1003
+ const result = isRecord4(accountList) ? accountList : {};
1004
+ const rateLimits = isRecord4(result["rateLimits"]) ? result["rateLimits"] : {};
1005
+ const entry = isRecord4(rateLimits[usageKey]) ? rateLimits[usageKey] : null;
1006
+ const account = isRecord4(result[usageKey]) ? result[usageKey] : null;
1007
+ const systemDefault = account && isRecord4(account["systemDefault"]) ? account["systemDefault"] : null;
1008
+ const accounts = account && Array.isArray(account["accounts"]) ? account["accounts"] : [];
1009
+ const hasAuth = systemDefault ? systemDefault["hasAuth"] === true : accounts.length ? true : null;
1010
+ if (!entry) return { status: "unknown", error: null, windows: [], exhausted: false, resetsAt: null, hasAuth };
1011
+ const windows = parseUsageWindows(entry);
1012
+ const exhaustedWindows = windows.filter((window) => window.usedPercent >= exhaustedPercent);
1013
+ const resetsAt = exhaustedWindows.map((window) => window.resetsAt).filter((value) => Boolean(value)).sort()[0] ?? null;
1014
+ return {
1015
+ status: entry["status"] === "ok" ? "ok" : entry["status"] === "unavailable" ? "unavailable" : "unknown",
1016
+ error: typeof entry["error"] === "string" ? entry["error"] : null,
1017
+ windows,
1018
+ exhausted: exhaustedWindows.length > 0,
1019
+ resetsAt,
1020
+ hasAuth
1021
+ };
1022
+ };
1023
+ var authStatusFor = (spec, usage, env) => {
1024
+ const hasEnvKey = spec.envKeys.some((key) => Boolean(env[key]?.trim()));
1025
+ if (spec.auth === "api-key") return hasEnvKey ? "ok" : "missing";
1026
+ if (spec.auth === "subscription") return usage.hasAuth === true || usage.status === "ok" ? "ok" : usage.hasAuth === false ? "missing" : hasEnvKey || usage.status === "unknown" ? "ok" : "unknown";
1027
+ return hasEnvKey || usage.status === "ok" ? "ok" : "unknown";
1028
+ };
1029
+ var runProbe = async (spec, binary, runner, timeoutMs) => {
1030
+ if (!spec.probe || !runner) return "skipped";
1031
+ const [head, ...rest] = spec.probe;
1032
+ const argv = [head === spec.bin ? binary : head ?? binary, ...rest];
1033
+ try {
1034
+ const outcome = await runner.run(argv, { timeoutMs });
1035
+ return outcome.code === 0 && !outcome.timedOut ? "passed" : "failed";
1036
+ } catch {
1037
+ return "failed";
1038
+ }
1039
+ };
1040
+ var detectProviders = async (input) => {
1041
+ const env = input.env ?? process.env;
1042
+ const platform = input.platform ?? process.platform;
1043
+ const now4 = input.now ?? (() => /* @__PURE__ */ new Date());
1044
+ const results = [];
1045
+ for (const spec of input.providers) {
1046
+ const binary = findExecutable(spec.bin, env, platform);
1047
+ const hookState = input.agentHooks[spec.id] ?? "unknown";
1048
+ const usage = parseProviderUsage(input.accountList, spec.orcaUsageKey, input.exhaustedPercent ?? 100);
1049
+ const auth = authStatusFor(spec, usage, env);
1050
+ const cooldown = input.cooldowns?.[spec.id] ?? null;
1051
+ const coolingDownUntil = cooldown && Date.parse(cooldown) > now4().getTime() ? new Date(cooldown).toISOString() : null;
1052
+ const reasons = [];
1053
+ if (!binary) reasons.push(`binary "${spec.bin}" not found on PATH`);
1054
+ if (auth === "missing") reasons.push(spec.auth === "api-key" ? `none of ${spec.envKeys.join(", ") || "the configured env keys"} is set` : `Orca reports no ${spec.id} credentials`);
1055
+ if (usage.exhausted) reasons.push(`usage exhausted${usage.resetsAt ? ` until ${usage.resetsAt}` : ""}`);
1056
+ if (coolingDownUntil) reasons.push(`cooling down until ${coolingDownUntil}`);
1057
+ const probe = binary && !reasons.length ? await runProbe(spec, binary, input.runner, input.probeTimeoutMs ?? 15e3) : "skipped";
1058
+ if (probe === "failed") reasons.push("probe command failed");
1059
+ results.push({ id: spec.id, binary, hookState, auth, usage, probe, coolingDownUntil, available: reasons.length === 0, reasons });
1060
+ }
1061
+ return results;
1062
+ };
1063
+ var cooldownUntil = (attempt, initialMin, maxMin, from, resetsAt = null) => {
1064
+ const minutes2 = Math.min(maxMin, initialMin * 2 ** Math.max(0, attempt));
1065
+ const backoff = from.getTime() + minutes2 * 6e4;
1066
+ const reset = resetsAt ? Date.parse(resetsAt) : Number.NaN;
1067
+ return new Date(Number.isFinite(reset) && reset > from.getTime() ? Math.max(reset, backoff) : backoff).toISOString();
1068
+ };
1069
+ var remainingUsagePercent = (usage, metric = "max") => {
1070
+ if (usage.status !== "ok" || !usage.windows.length) return null;
1071
+ const windows = metric === "max" ? usage.windows : usage.windows.filter((window) => window.kind === metric);
1072
+ const pool = windows.length ? windows : usage.windows;
1073
+ if (!pool.length) return null;
1074
+ const worst = Math.max(...pool.map((window) => window.usedPercent));
1075
+ return Math.max(0, Math.min(100, 100 - worst));
1076
+ };
1077
+ var usageRankTuple = (usage, metric, preferKnownUsage) => {
1078
+ const remaining = remainingUsagePercent(usage, metric);
1079
+ const known = remaining === null ? 1 : 0;
1080
+ const remainingKey = remaining === null ? 0 : -remaining;
1081
+ const resetMs = usage.resetsAt ? Date.parse(usage.resetsAt) : Number.POSITIVE_INFINITY;
1082
+ return preferKnownUsage ? [known, remainingKey, resetMs] : [remainingKey, known, resetMs];
1083
+ };
1084
+ var ORCA_META_KEYS = /* @__PURE__ */ new Set([
1085
+ "minimaxCookieConfigured",
1086
+ "minimaxApiKeyConfigured",
1087
+ "grokAuthConfigured",
1088
+ "claudeTarget",
1089
+ "codexTarget",
1090
+ "inactiveClaudeAccounts",
1091
+ "inactiveCodexAccounts"
1092
+ ]);
1093
+ var listOrcaIntegratedProviderKeys = (accountList) => {
1094
+ const result = isRecord4(accountList) ? accountList : {};
1095
+ const rateLimits = isRecord4(result["rateLimits"]) ? result["rateLimits"] : {};
1096
+ const keys = /* @__PURE__ */ new Set();
1097
+ for (const key of Object.keys(rateLimits)) {
1098
+ if (!ORCA_META_KEYS.has(key) && isRecord4(rateLimits[key])) keys.add(key);
1099
+ }
1100
+ for (const key of Object.keys(result)) {
1101
+ if (key === "rateLimits" || ORCA_META_KEYS.has(key)) continue;
1102
+ if (isRecord4(result[key])) keys.add(key);
1103
+ }
1104
+ return [...keys].sort();
1105
+ };
1106
+ var undeclaredOrcaProviders = (accountList, declared) => {
1107
+ const usageKeyToId = /* @__PURE__ */ new Map();
1108
+ for (const [id2, settings] of Object.entries(declared)) {
1109
+ usageKeyToId.set(settings.orcaUsageKey ?? id2, id2);
1110
+ usageKeyToId.set(id2, id2);
1111
+ }
1112
+ usageKeyToId.set("opencodeGo", usageKeyToId.get("opencodeGo") ?? "opencode");
1113
+ return listOrcaIntegratedProviderKeys(accountList).filter((key) => !usageKeyToId.has(key));
1114
+ };
1115
+
1116
+ // src/adapters/rag-context.ts
1117
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
959
1118
  var requiredString2 = (value, label) => {
960
1119
  if (typeof value !== "string" || !value.trim()) return fail(`${label} must be a non-empty string.`, "INVALID_INPUT");
961
1120
  return value;
962
1121
  };
963
1122
  var parseReference = (value, index2) => {
964
- if (!isRecord4(value)) return fail(`RAG references[${index2}] must be an object.`, "INVALID_INPUT");
1123
+ if (!isRecord5(value)) return fail(`RAG references[${index2}] must be an object.`, "INVALID_INPUT");
965
1124
  const relevance = value["relevance"];
966
1125
  if (relevance !== void 0 && (typeof relevance !== "number" || relevance < 0 || relevance > 1)) return fail(`RAG references[${index2}].relevance must be between 0 and 1.`, "INVALID_INPUT");
967
1126
  return {
@@ -974,7 +1133,7 @@ var parseReference = (value, index2) => {
974
1133
  };
975
1134
  };
976
1135
  var parseRagQueryOutput = (value) => {
977
- if (!isRecord4(value)) return fail("RAG query output must be a JSON object.", "INVALID_INPUT");
1136
+ if (!isRecord5(value)) return fail("RAG query output must be a JSON object.", "INVALID_INPUT");
978
1137
  const rawReferences = value["references"];
979
1138
  if (!Array.isArray(rawReferences)) return fail("RAG query output.references must be an array.", "INVALID_INPUT");
980
1139
  const references = rawReferences.map((entry, index2) => parseReference(entry, index2));
@@ -1389,7 +1548,7 @@ var artifactId = (value) => {
1389
1548
  if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(result)) fail("Artifact artifactId is invalid.", "INVALID_INPUT");
1390
1549
  return result;
1391
1550
  };
1392
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1551
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1393
1552
  var artifactBody = (artifact) => ({
1394
1553
  type: artifact.type,
1395
1554
  schemaVersion: artifact.schemaVersion,
@@ -1408,7 +1567,7 @@ var artifactBody = (artifact) => ({
1408
1567
  });
1409
1568
  var expectedArtifactHash = (artifact) => hashJson(artifactBody(artifact));
1410
1569
  var validateArtifactEnvelope = (value) => {
1411
- if (!isRecord5(value)) return fail("Artifact envelope must be an object.", "INVALID_INPUT");
1570
+ if (!isRecord6(value)) return fail("Artifact envelope must be an object.", "INVALID_INPUT");
1412
1571
  if (value["type"] !== "agentskit-harness-artifact" || value["schemaVersion"] !== ARTIFACT_SCHEMA_VERSION) fail("Artifact envelope type or schemaVersion is invalid.", "INVALID_INPUT");
1413
1572
  if (!ARTIFACT_TYPES.includes(value["artifactType"])) fail("Artifact artifactType is invalid.", "INVALID_INPUT");
1414
1573
  if (!Number.isInteger(value["artifactVersion"]) || value["artifactVersion"] < 1) fail("Artifact artifactVersion must be a positive integer.", "INVALID_INPUT");
@@ -2123,44 +2282,9 @@ var readEvidenceTrustStore = (path) => {
2123
2282
  return key;
2124
2283
  });
2125
2284
  };
2126
- var executable = (path) => {
2127
- try {
2128
- return statSync(path).isFile();
2129
- } catch {
2130
- return false;
2131
- }
2132
- };
2133
- var findExecutable = (name2, env = process.env, platform = process.platform) => {
2134
- if (typeof name2 !== "string" || !name2.trim()) return null;
2135
- if (isAbsolute(name2) || name2.includes("/") || name2.includes("\\")) return existsSync(name2) && executable(name2) ? name2 : null;
2136
- const extensions = platform === "win32" ? (env["PATHEXT"] ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean) : [""];
2137
- for (const dir of (env["PATH"] ?? "").split(delimiter).filter(Boolean)) {
2138
- for (const extension of extensions) {
2139
- const candidate = join(dir, `${name2}${extension}`);
2140
- if (executable(candidate)) return candidate;
2141
- }
2142
- if (platform === "win32" && executable(join(dir, name2))) return join(dir, name2);
2143
- }
2144
- return null;
2145
- };
2146
- var parseJsonEnvelope = (stdout) => {
2147
- const trimmed = stdout.trim();
2148
- if (!trimmed) return null;
2149
- let parsed;
2150
- try {
2151
- parsed = JSON.parse(trimmed);
2152
- } catch {
2153
- return null;
2154
- }
2155
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
2156
- const record3 = parsed;
2157
- if (typeof record3.ok !== "boolean") return null;
2158
- const error = typeof record3.error === "string" ? record3.error : typeof record3.error === "object" && record3.error !== null && typeof record3.error.message === "string" ? record3.error.message : void 0;
2159
- return { ok: record3.ok, result: record3.result, ...error === void 0 ? {} : { error } };
2160
- };
2161
2285
 
2162
2286
  // src/adapters/orca-cli.ts
2163
- var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2287
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2164
2288
  var str = (value, fallback = "") => typeof value === "string" ? value : fallback;
2165
2289
  var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
2166
2290
  var compareVersions = (left, right) => {
@@ -2174,9 +2298,9 @@ var compareVersions = (left, right) => {
2174
2298
  };
2175
2299
  var parseOrcaVersion = (stdout) => stdout.match(/\d+\.\d+\.\d+/)?.[0] ?? null;
2176
2300
  var parseOrcaStatus = (result) => {
2177
- const record3 = isRecord6(result) ? result : {};
2178
- const app = isRecord6(record3["app"]) ? record3["app"] : {};
2179
- const runtime = isRecord6(record3["runtime"]) ? record3["runtime"] : {};
2301
+ const record3 = isRecord7(result) ? result : {};
2302
+ const app = isRecord7(record3["app"]) ? record3["app"] : {};
2303
+ const runtime = isRecord7(record3["runtime"]) ? record3["runtime"] : {};
2180
2304
  return {
2181
2305
  appRunning: app["running"] === true,
2182
2306
  runtimeReady: runtime["state"] === "ready" && runtime["reachable"] === true,
@@ -2187,14 +2311,14 @@ var parseOrcaStatus = (result) => {
2187
2311
  };
2188
2312
  var linkedLinear = (value) => {
2189
2313
  if (typeof value === "string" && value.trim()) return value.trim();
2190
- if (isRecord6(value)) {
2314
+ if (isRecord7(value)) {
2191
2315
  for (const key of ["identifier", "id", "url"]) if (typeof value[key] === "string" && value[key].trim()) return value[key].trim();
2192
2316
  }
2193
2317
  return null;
2194
2318
  };
2195
2319
  var parseOrcaWorktrees = (result) => {
2196
- const list2 = isRecord6(result) && Array.isArray(result["worktrees"]) ? result["worktrees"] : Array.isArray(result) ? result : [];
2197
- return list2.filter(isRecord6).map((item) => ({
2320
+ const list2 = isRecord7(result) && Array.isArray(result["worktrees"]) ? result["worktrees"] : Array.isArray(result) ? result : [];
2321
+ return list2.filter(isRecord7).map((item) => ({
2198
2322
  id: str(item["worktreeId"], str(item["id"])),
2199
2323
  repoId: str(item["repoId"]),
2200
2324
  repo: str(item["repo"]),
@@ -2211,8 +2335,8 @@ var parseOrcaWorktrees = (result) => {
2211
2335
  })).filter((item) => item.id);
2212
2336
  };
2213
2337
  var parseOrcaAgentHooks = (result) => {
2214
- const statuses = isRecord6(result) && Array.isArray(result["statuses"]) ? result["statuses"] : [];
2215
- return Object.fromEntries(statuses.filter(isRecord6).flatMap((item) => {
2338
+ const statuses = isRecord7(result) && Array.isArray(result["statuses"]) ? result["statuses"] : [];
2339
+ return Object.fromEntries(statuses.filter(isRecord7).flatMap((item) => {
2216
2340
  const agent = str(item["agent"]);
2217
2341
  if (!agent) return [];
2218
2342
  const state = item["state"] === "installed" ? "installed" : item["state"] === "not_installed" ? "not_installed" : "unknown";
@@ -2238,9 +2362,9 @@ var orcaWorktrees = async (runner, options2 = {}) => parseOrcaWorktrees(await or
2238
2362
  var orcaAgentHooks = async (runner, options2 = {}) => parseOrcaAgentHooks(await orcaJson(runner, ["agent", "hooks", "status"], options2));
2239
2363
  var orcaAccountList = async (runner, options2 = {}) => orcaJson(runner, ["account", "list"], options2);
2240
2364
  var parseOrcaWorktreeCreate = (result) => {
2241
- const record3 = isRecord6(result) ? result : {};
2242
- const nested = isRecord6(record3["worktree"]) ? record3["worktree"] : record3;
2243
- const startup = isRecord6(record3["startupTerminal"]) ? record3["startupTerminal"] : isRecord6(nested["startupTerminal"]) ? nested["startupTerminal"] : {};
2365
+ const record3 = isRecord7(result) ? result : {};
2366
+ const nested = isRecord7(record3["worktree"]) ? record3["worktree"] : record3;
2367
+ const startup = isRecord7(record3["startupTerminal"]) ? record3["startupTerminal"] : isRecord7(nested["startupTerminal"]) ? nested["startupTerminal"] : {};
2244
2368
  const id2 = str(nested["worktreeId"], str(nested["id"], str(record3["worktreeId"], str(record3["id"]))));
2245
2369
  if (!id2) fail("orca worktree create returned no worktree id.", "HARNESS_ERROR");
2246
2370
  return {
@@ -2270,8 +2394,8 @@ var orcaWorktreeSetArgv = (input, bin = "orca") => [
2270
2394
  var orcaWorktreeSet = async (runner, input, options2 = {}) => orcaJson(runner, orcaWorktreeSetArgv(input).slice(1), options2);
2271
2395
  var orcaWorktreeRemove = async (runner, input, options2 = {}) => orcaJson(runner, ["worktree", "rm", "--worktree", input.worktree, ...input.force ? ["--force"] : []], { ...options2, timeoutMs: options2.timeoutMs ?? 6e4 });
2272
2396
  var parseOrcaTerminals = (result) => {
2273
- const list2 = isRecord6(result) ? Array.isArray(result["terminals"]) ? result["terminals"] : Array.isArray(result["items"]) ? result["items"] : [] : Array.isArray(result) ? result : [];
2274
- return list2.filter(isRecord6).map((item) => ({
2397
+ const list2 = isRecord7(result) ? Array.isArray(result["terminals"]) ? result["terminals"] : Array.isArray(result["items"]) ? result["items"] : [] : Array.isArray(result) ? result : [];
2398
+ return list2.filter(isRecord7).map((item) => ({
2275
2399
  handle: str(item["handle"], str(item["id"])),
2276
2400
  title: str(item["title"], str(item["name"])),
2277
2401
  worktreeId: str(item["worktreeId"], str(item["worktree"])) || null,
@@ -2286,29 +2410,29 @@ var parseOrcaTerminals = (result) => {
2286
2410
  var orcaTerminalList = async (runner, input = {}, options2 = {}) => parseOrcaTerminals(await orcaJson(runner, ["terminal", "list", ...input.worktree ? ["--worktree", input.worktree] : [], ...input.limit ? ["--limit", String(input.limit)] : []], options2));
2287
2411
  var orcaTerminalCreate = async (runner, input, options2 = {}) => {
2288
2412
  const result = await orcaJson(runner, ["terminal", "create", "--worktree", input.worktree, "--command", input.command, ...input.title ? ["--title", input.title] : []], { ...options2, timeoutMs: options2.timeoutMs ?? 6e4 });
2289
- const record3 = isRecord6(result) ? result : {};
2290
- const terminal2 = isRecord6(record3["terminal"]) ? record3["terminal"] : record3;
2413
+ const record3 = isRecord7(result) ? result : {};
2414
+ const terminal2 = isRecord7(record3["terminal"]) ? record3["terminal"] : record3;
2291
2415
  const handle = str(terminal2["handle"], str(record3["handle"]));
2292
2416
  if (!handle) fail("orca terminal create returned no terminal handle.", "HARNESS_ERROR");
2293
2417
  return { handle, raw: result };
2294
2418
  };
2295
2419
  var parseOrcaSendReceipt = (result) => {
2296
- const record3 = isRecord6(result) ? result : {};
2297
- const receipt = isRecord6(record3["receipt"]) ? record3["receipt"] : record3;
2298
- const stages = Array.isArray(receipt["stages"]) ? receipt["stages"].map((stage) => isRecord6(stage) ? str(stage["stage"], str(stage["name"])) : str(stage)).filter(Boolean) : [];
2420
+ const record3 = isRecord7(result) ? result : {};
2421
+ const receipt = isRecord7(record3["receipt"]) ? record3["receipt"] : record3;
2422
+ const stages = Array.isArray(receipt["stages"]) ? receipt["stages"].map((stage) => isRecord7(stage) ? str(stage["stage"], str(stage["name"])) : str(stage)).filter(Boolean) : [];
2299
2423
  const accepted = receipt["accepted"] === false ? false : receipt["accepted"] === true || stages.includes("input_accepted") || (result === null || result === void 0 || Object.keys(record3).length === 0);
2300
- return { accepted, requestId: str(receipt["requestId"], str(record3["requestId"])) || null, stages, warnings: Array.isArray(record3["warnings"]) ? record3["warnings"].map((warning) => isRecord6(warning) ? str(warning["message"], JSON.stringify(warning)) : str(warning)) : [] };
2424
+ return { accepted, requestId: str(receipt["requestId"], str(record3["requestId"])) || null, stages, warnings: Array.isArray(record3["warnings"]) ? record3["warnings"].map((warning) => isRecord7(warning) ? str(warning["message"], JSON.stringify(warning)) : str(warning)) : [] };
2301
2425
  };
2302
2426
  var orcaTerminalSend = async (runner, input, options2 = {}) => parseOrcaSendReceipt(await orcaJson(runner, ["terminal", "send", "--terminal", input.terminal, "--text", input.text, ...input.enter === false ? [] : ["--enter"], ...input.waitSubmitSeconds ? ["--wait-submit", String(input.waitSubmitSeconds)] : []], { ...options2, timeoutMs: options2.timeoutMs ?? (input.waitSubmitSeconds ?? 0) * 1e3 + 3e4 }));
2303
2427
  var orcaTerminalWait = async (runner, input, options2 = {}) => {
2304
2428
  const result = await orcaJson(runner, ["terminal", "wait", "--terminal", input.terminal, "--for", input.for, "--timeout-ms", String(input.timeoutMs)], { ...options2, timeoutMs: input.timeoutMs + 15e3 });
2305
- const record3 = isRecord6(result) ? result : {};
2306
- const wait = isRecord6(record3["wait"]) ? record3["wait"] : record3;
2429
+ const record3 = isRecord7(result) ? result : {};
2430
+ const wait = isRecord7(record3["wait"]) ? record3["wait"] : record3;
2307
2431
  return { satisfied: wait["satisfied"] === true, raw: result };
2308
2432
  };
2309
2433
  var parseOrcaAutomations = (result) => {
2310
- const list2 = isRecord6(result) ? Array.isArray(result["automations"]) ? result["automations"] : Array.isArray(result["items"]) ? result["items"] : [] : Array.isArray(result) ? result : [];
2311
- return list2.filter(isRecord6).map((item) => ({ id: str(item["id"]), name: str(item["name"]), enabled: item["enabled"] !== false && item["disabled"] !== true, trigger: str(item["rrule"], str(item["trigger"], str(item["schedule"], typeof item["schedule"] === "object" && item["schedule"] !== null ? JSON.stringify(item["schedule"]) : ""))), provider: str(item["agentId"], str(item["provider"], str(item["agent"]))) || null, raw: item })).filter((item) => item.id);
2434
+ const list2 = isRecord7(result) ? Array.isArray(result["automations"]) ? result["automations"] : Array.isArray(result["items"]) ? result["items"] : [] : Array.isArray(result) ? result : [];
2435
+ return list2.filter(isRecord7).map((item) => ({ id: str(item["id"]), name: str(item["name"]), enabled: item["enabled"] !== false && item["disabled"] !== true, trigger: str(item["rrule"], str(item["trigger"], str(item["schedule"], typeof item["schedule"] === "object" && item["schedule"] !== null ? JSON.stringify(item["schedule"]) : ""))), provider: str(item["agentId"], str(item["provider"], str(item["agent"]))) || null, raw: item })).filter((item) => item.id);
2312
2436
  };
2313
2437
  var orcaAutomationsList = async (runner, options2 = {}) => parseOrcaAutomations(await orcaJson(runner, ["automations", "list"], options2));
2314
2438
  var orcaAutomationCreateArgv = (spec, bin = "orca") => [
@@ -2355,84 +2479,6 @@ var orcaAutomationEditArgv = (id2, spec, bin = "orca") => [
2355
2479
  var orcaAutomationRemove = async (runner, id2, options2 = {}) => orcaJson(runner, ["automations", "remove", id2], options2);
2356
2480
  var orcaAutomationRuns = async (runner, id2, options2 = {}) => orcaJson(runner, ["automations", "runs", "--id", id2], options2);
2357
2481
 
2358
- // src/adapters/providers.ts
2359
- var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2360
- var iso = (value) => typeof value === "number" && Number.isFinite(value) ? new Date(value).toISOString() : typeof value === "string" && !Number.isNaN(Date.parse(value)) ? new Date(value).toISOString() : null;
2361
- var parseUsageWindows = (entry) => {
2362
- if (!isRecord7(entry)) return [];
2363
- return Object.entries(entry).flatMap(([kind, value]) => {
2364
- if (!isRecord7(value) || typeof value["usedPercent"] !== "number") return [];
2365
- return [{ kind, usedPercent: value["usedPercent"], windowMinutes: typeof value["windowMinutes"] === "number" ? value["windowMinutes"] : null, resetsAt: iso(value["resetsAt"]) }];
2366
- });
2367
- };
2368
- var parseProviderUsage = (accountList, usageKey, exhaustedPercent = 100) => {
2369
- const result = isRecord7(accountList) ? accountList : {};
2370
- const rateLimits = isRecord7(result["rateLimits"]) ? result["rateLimits"] : {};
2371
- const entry = isRecord7(rateLimits[usageKey]) ? rateLimits[usageKey] : null;
2372
- const account = isRecord7(result[usageKey]) ? result[usageKey] : null;
2373
- const systemDefault = account && isRecord7(account["systemDefault"]) ? account["systemDefault"] : null;
2374
- const accounts = account && Array.isArray(account["accounts"]) ? account["accounts"] : [];
2375
- const hasAuth = systemDefault ? systemDefault["hasAuth"] === true : accounts.length ? true : null;
2376
- if (!entry) return { status: "unknown", error: null, windows: [], exhausted: false, resetsAt: null, hasAuth };
2377
- const windows = parseUsageWindows(entry);
2378
- const exhaustedWindows = windows.filter((window) => window.usedPercent >= exhaustedPercent);
2379
- const resetsAt = exhaustedWindows.map((window) => window.resetsAt).filter((value) => Boolean(value)).sort()[0] ?? null;
2380
- return {
2381
- status: entry["status"] === "ok" ? "ok" : entry["status"] === "unavailable" ? "unavailable" : "unknown",
2382
- error: typeof entry["error"] === "string" ? entry["error"] : null,
2383
- windows,
2384
- exhausted: exhaustedWindows.length > 0,
2385
- resetsAt,
2386
- hasAuth
2387
- };
2388
- };
2389
- var authStatusFor = (spec, usage, env) => {
2390
- const hasEnvKey = spec.envKeys.some((key) => Boolean(env[key]?.trim()));
2391
- if (spec.auth === "api-key") return hasEnvKey ? "ok" : "missing";
2392
- if (spec.auth === "subscription") return usage.hasAuth === true || usage.status === "ok" ? "ok" : usage.hasAuth === false ? "missing" : hasEnvKey || usage.status === "unknown" ? "ok" : "unknown";
2393
- return hasEnvKey || usage.status === "ok" ? "ok" : "unknown";
2394
- };
2395
- var runProbe = async (spec, binary, runner, timeoutMs) => {
2396
- if (!spec.probe || !runner) return "skipped";
2397
- const [head, ...rest] = spec.probe;
2398
- const argv = [head === spec.bin ? binary : head ?? binary, ...rest];
2399
- try {
2400
- const outcome = await runner.run(argv, { timeoutMs });
2401
- return outcome.code === 0 && !outcome.timedOut ? "passed" : "failed";
2402
- } catch {
2403
- return "failed";
2404
- }
2405
- };
2406
- var detectProviders = async (input) => {
2407
- const env = input.env ?? process.env;
2408
- const platform = input.platform ?? process.platform;
2409
- const now4 = input.now ?? (() => /* @__PURE__ */ new Date());
2410
- const results = [];
2411
- for (const spec of input.providers) {
2412
- const binary = findExecutable(spec.bin, env, platform);
2413
- const hookState = input.agentHooks[spec.id] ?? "unknown";
2414
- const usage = parseProviderUsage(input.accountList, spec.orcaUsageKey, input.exhaustedPercent ?? 100);
2415
- const auth = authStatusFor(spec, usage, env);
2416
- const cooldown = input.cooldowns?.[spec.id] ?? null;
2417
- const coolingDownUntil = cooldown && Date.parse(cooldown) > now4().getTime() ? new Date(cooldown).toISOString() : null;
2418
- const reasons = [];
2419
- if (!binary) reasons.push(`binary "${spec.bin}" not found on PATH`);
2420
- if (auth === "missing") reasons.push(spec.auth === "api-key" ? `none of ${spec.envKeys.join(", ") || "the configured env keys"} is set` : `Orca reports no ${spec.id} credentials`);
2421
- if (usage.exhausted) reasons.push(`usage exhausted${usage.resetsAt ? ` until ${usage.resetsAt}` : ""}`);
2422
- if (coolingDownUntil) reasons.push(`cooling down until ${coolingDownUntil}`);
2423
- const probe = binary && !reasons.length ? await runProbe(spec, binary, input.runner, input.probeTimeoutMs ?? 15e3) : "skipped";
2424
- if (probe === "failed") reasons.push("probe command failed");
2425
- results.push({ id: spec.id, binary, hookState, auth, usage, probe, coolingDownUntil, available: reasons.length === 0, reasons });
2426
- }
2427
- return results;
2428
- };
2429
- var cooldownUntil = (attempt, initialMin, maxMin, from, resetsAt = null) => {
2430
- const minutes2 = Math.min(maxMin, initialMin * 2 ** Math.max(0, attempt));
2431
- const backoff = from.getTime() + minutes2 * 6e4;
2432
- const reset = resetsAt ? Date.parse(resetsAt) : Number.NaN;
2433
- return new Date(Number.isFinite(reset) && reset > from.getTime() ? Math.max(reset, backoff) : backoff).toISOString();
2434
- };
2435
-
2436
2482
  // src/adapters/linear-orca.ts
2437
2483
  var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2438
2484
  var str2 = (value, fallback = "") => typeof value === "string" ? value : fallback;
@@ -2583,6 +2629,41 @@ var LoopConfigSchema = z.object({
2583
2629
  reviewer: tiers,
2584
2630
  builder: tiers,
2585
2631
  watcher: tiers,
2632
+ /** How candidates are ordered. `tiers` = YAML order (0.6 behaviour). `hybrid` = keep tiers, rank by remaining usage inside each. `dynamic` = flatten + usage. `catalog` = discover models via CLI/AA/builtin + usage. */
2633
+ routing: z.object({
2634
+ mode: z.enum(["tiers", "hybrid", "dynamic", "catalog"]).default("tiers"),
2635
+ /** Which usage window drives remaining%. `max` = most constrained window. */
2636
+ usageMetric: z.enum(["max", "session", "weekly", "monthly"]).default("max"),
2637
+ /** Prefer providers with live usage % over those with unknown usage (e.g. grok often has no %). */
2638
+ preferKnownUsage: z.boolean().default(true),
2639
+ excludeProviders: z.array(nonEmpty2).default([]),
2640
+ /** If non-empty, only these providers may be selected (still must be declared under providers). */
2641
+ includeProviders: z.array(nonEmpty2).default([]),
2642
+ /** Hard pin per role (`provider/model`). If pinned provider is unavailable, fall through unless pinStrict. */
2643
+ pin: z.object({
2644
+ orchestrator: modelRef.optional(),
2645
+ reviewer: modelRef.optional(),
2646
+ builder: modelRef.optional(),
2647
+ watcher: modelRef.optional()
2648
+ }).prefault({}),
2649
+ pinStrict: z.boolean().default(false)
2650
+ }).prefault({}),
2651
+ /** Quality band when `routing.mode: catalog` (and as soft bias in hybrid). */
2652
+ roles: z.object({
2653
+ orchestrator: z.object({ quality: z.enum(["frontier", "balanced", "fast"]).default("frontier"), preferCreators: z.array(nonEmpty2).default([]) }).prefault({}),
2654
+ reviewer: z.object({ quality: z.enum(["frontier", "balanced", "fast"]).default("frontier"), preferCreators: z.array(nonEmpty2).default([]) }).prefault({}),
2655
+ builder: z.object({ quality: z.enum(["frontier", "balanced", "fast"]).default("balanced"), preferCreators: z.array(nonEmpty2).default([]) }).prefault({}),
2656
+ watcher: z.object({ quality: z.enum(["frontier", "balanced", "fast"]).default("fast"), preferCreators: z.array(nonEmpty2).default([]) }).prefault({})
2657
+ }).prefault({}),
2658
+ catalog: z.object({
2659
+ sources: z.array(z.enum(["cli", "artificial-analysis", "builtin"])).default(["cli", "builtin"]),
2660
+ artificialAnalysis: z.object({
2661
+ enabled: z.boolean().default(false),
2662
+ apiKeyEnv: nonEmpty2.default("ARTIFICIAL_ANALYSIS_API_KEY"),
2663
+ cacheHours: z.number().positive().default(24),
2664
+ endpoint: nonEmpty2.default("https://artificialanalysis.ai/api/v2/data/llms/models")
2665
+ }).prefault({})
2666
+ }).prefault({}),
2586
2667
  cooldown: z.object({
2587
2668
  initialMin: z.number().int().positive().default(30),
2588
2669
  maxMin: z.number().int().positive().default(240),
@@ -2871,28 +2952,395 @@ var assessSlots = (input) => {
2871
2952
  };
2872
2953
 
2873
2954
  // src/loop/routing.ts
2874
- var selectModel = (config, role, availability) => {
2955
+ var allowedProvider = (config, providerId) => {
2956
+ const { excludeProviders, includeProviders } = config.models.routing;
2957
+ if (excludeProviders.includes(providerId)) return false;
2958
+ if (includeProviders.length && !includeProviders.includes(providerId)) return false;
2959
+ return true;
2960
+ };
2961
+ var materialize = (config, ref, tier, preferenceIndex, availability, reason) => {
2962
+ const identity = providerIdentity(config, ref.provider);
2963
+ return {
2964
+ ...ref,
2965
+ tier,
2966
+ preferenceIndex,
2967
+ orcaAgent: identity.orcaAgent,
2968
+ tui: renderTuiCommand(identity.settings, ref.model),
2969
+ remainingPercent: availability ? remainingUsagePercent(availability.usage, config.models.routing.usageMetric) : null,
2970
+ reason
2971
+ };
2972
+ };
2973
+ var compareUsageAware = (config, left, right, byId) => {
2974
+ const leftAv = byId.get(left.provider);
2975
+ const rightAv = byId.get(right.provider);
2976
+ const leftTuple = leftAv ? usageRankTuple(leftAv.usage, config.models.routing.usageMetric, config.models.routing.preferKnownUsage) : [1, 0, Number.POSITIVE_INFINITY];
2977
+ const rightTuple = rightAv ? usageRankTuple(rightAv.usage, config.models.routing.usageMetric, config.models.routing.preferKnownUsage) : [1, 0, Number.POSITIVE_INFINITY];
2978
+ for (let i = 0; i < leftTuple.length; i += 1) {
2979
+ if (leftTuple[i] !== rightTuple[i]) return leftTuple[i] - rightTuple[i];
2980
+ }
2981
+ if (left.preferenceIndex !== right.preferenceIndex) return left.preferenceIndex - right.preferenceIndex;
2982
+ return left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model);
2983
+ };
2984
+ var availableFromTiers = (config, role, availability) => {
2875
2985
  const byId = new Map(availability.map((item) => [item.id, item]));
2876
2986
  const skipped = [];
2987
+ const ranked = [];
2988
+ let preferenceIndex = 0;
2877
2989
  for (const [tier, refs] of tiersFor(config, role).entries()) {
2878
2990
  for (const ref of refs) {
2991
+ const index2 = preferenceIndex;
2992
+ preferenceIndex += 1;
2993
+ if (!allowedProvider(config, ref.provider)) {
2994
+ skipped.push({ tier, ref, reasons: ["provider excluded by models.routing"] });
2995
+ continue;
2996
+ }
2879
2997
  const provider = byId.get(ref.provider);
2880
2998
  if (provider?.available) {
2881
- const identity = providerIdentity(config, ref.provider);
2882
- return { role, selected: { ...ref, tier, orcaAgent: identity.orcaAgent, tui: renderTuiCommand(identity.settings, ref.model) }, skipped };
2999
+ ranked.push(materialize(config, ref, tier, index2, provider, `yaml tier ${tier + 1}`));
3000
+ } else {
3001
+ skipped.push({ tier, ref, reasons: provider ? provider.reasons : ["provider was not detected"] });
2883
3002
  }
2884
- skipped.push({ tier, ref, reasons: provider ? provider.reasons : ["provider was not detected"] });
2885
3003
  }
2886
3004
  }
2887
- return { role, selected: null, skipped };
3005
+ return { ranked, skipped };
2888
3006
  };
2889
- var routeAllRoles = (config, availability) => Object.fromEntries(MODEL_ROLES.map((role) => [role, selectModel(config, role, availability)]));
2890
- var rankModels = (config, role, availability) => {
3007
+ var applyPin = (config, role, availability, skipped) => {
3008
+ const pin = config.models.routing.pin[role];
3009
+ if (!pin) return null;
3010
+ const ref = parseModelRef(pin);
2891
3011
  const byId = new Map(availability.map((item) => [item.id, item]));
2892
- return tiersFor(config, role).flatMap((refs, tier) => refs.filter((ref) => byId.get(ref.provider)?.available).map((ref) => {
2893
- const identity = providerIdentity(config, ref.provider);
2894
- return { ...ref, tier, orcaAgent: identity.orcaAgent, tui: renderTuiCommand(identity.settings, ref.model) };
2895
- }));
3012
+ const provider = byId.get(ref.provider);
3013
+ if (provider?.available && allowedProvider(config, ref.provider)) {
3014
+ return materialize(config, ref, -1, -1, provider, `pinned ${pin}`);
3015
+ }
3016
+ skipped.push({ tier: -1, ref, reasons: provider ? provider.reasons : ["pinned provider was not detected"] });
3017
+ if (config.models.routing.pinStrict) return null;
3018
+ return null;
3019
+ };
3020
+ var selectModel = (config, role, availability, extraCandidates = []) => {
3021
+ const byId = new Map(availability.map((item) => [item.id, item]));
3022
+ const mode = config.models.routing.mode;
3023
+ const { ranked: fromYaml, skipped } = availableFromTiers(config, role, availability);
3024
+ if (config.models.routing.pin[role]) {
3025
+ const pinned = applyPin(config, role, availability, skipped);
3026
+ if (pinned) return { role, selected: pinned, skipped };
3027
+ if (config.models.routing.pinStrict) return { role, selected: null, skipped };
3028
+ }
3029
+ const extras = [];
3030
+ let extraIndex = 1e4;
3031
+ for (const ref of extraCandidates) {
3032
+ if (!allowedProvider(config, ref.provider)) continue;
3033
+ const provider = byId.get(ref.provider);
3034
+ if (!provider?.available) continue;
3035
+ extras.push(materialize(config, ref, 99, extraIndex, provider, "catalog"));
3036
+ extraIndex += 1;
3037
+ }
3038
+ if (mode === "tiers") {
3039
+ const first = fromYaml[0] ?? extras[0] ?? null;
3040
+ return { role, selected: first ?? null, skipped };
3041
+ }
3042
+ if (mode === "hybrid") {
3043
+ const byTier = /* @__PURE__ */ new Map();
3044
+ for (const item of fromYaml) {
3045
+ const list2 = byTier.get(item.tier) ?? [];
3046
+ list2.push(item);
3047
+ byTier.set(item.tier, list2);
3048
+ }
3049
+ const tiers2 = [...byTier.keys()].sort((a, b) => a - b);
3050
+ for (const tier of tiers2) {
3051
+ const pool2 = byTier.get(tier) ?? [];
3052
+ pool2.sort((left, right) => compareUsageAware(config, left, right, byId));
3053
+ if (pool2[0]) {
3054
+ return {
3055
+ role,
3056
+ selected: {
3057
+ ...pool2[0],
3058
+ reason: `hybrid tier ${tier + 1} \xB7 remaining ${pool2[0].remainingPercent ?? "unknown"}%`
3059
+ },
3060
+ skipped
3061
+ };
3062
+ }
3063
+ }
3064
+ if (extras.length) {
3065
+ extras.sort((left, right) => compareUsageAware(config, left, right, byId));
3066
+ return { role, selected: { ...extras[0], reason: `hybrid catalog \xB7 remaining ${extras[0].remainingPercent ?? "unknown"}%` }, skipped };
3067
+ }
3068
+ return { role, selected: null, skipped };
3069
+ }
3070
+ const pool = [...fromYaml, ...extras];
3071
+ pool.sort((left, right) => compareUsageAware(config, left, right, byId));
3072
+ const best = pool[0] ?? null;
3073
+ return {
3074
+ role,
3075
+ selected: best ? { ...best, reason: `${mode} \xB7 remaining ${best.remainingPercent ?? "unknown"}% \xB7 ${best.reason}` } : null,
3076
+ skipped
3077
+ };
3078
+ };
3079
+ var routeAllRoles = (config, availability, extrasByRole = {}) => Object.fromEntries(MODEL_ROLES.map((role) => [role, selectModel(config, role, availability, extrasByRole[role] ?? [])]));
3080
+ var rankModels = (config, role, availability, extraCandidates = []) => {
3081
+ const byId = new Map(availability.map((item) => [item.id, item]));
3082
+ const { ranked } = availableFromTiers(config, role, availability);
3083
+ const extras = [];
3084
+ let extraIndex = 1e4;
3085
+ for (const ref of extraCandidates) {
3086
+ if (!allowedProvider(config, ref.provider)) continue;
3087
+ const provider = byId.get(ref.provider);
3088
+ if (!provider?.available) continue;
3089
+ extras.push(materialize(config, ref, 99, extraIndex, provider, "catalog"));
3090
+ extraIndex += 1;
3091
+ }
3092
+ const mode = config.models.routing.mode;
3093
+ if (mode === "tiers") return [...ranked, ...extras];
3094
+ if (mode === "hybrid") {
3095
+ const byTier = /* @__PURE__ */ new Map();
3096
+ for (const item of ranked) {
3097
+ const list2 = byTier.get(item.tier) ?? [];
3098
+ list2.push(item);
3099
+ byTier.set(item.tier, list2);
3100
+ }
3101
+ const ordered = [];
3102
+ for (const tier of [...byTier.keys()].sort((a, b) => a - b)) {
3103
+ const pool2 = byTier.get(tier) ?? [];
3104
+ pool2.sort((left, right) => compareUsageAware(config, left, right, byId));
3105
+ ordered.push(...pool2);
3106
+ }
3107
+ extras.sort((left, right) => compareUsageAware(config, left, right, byId));
3108
+ return [...ordered, ...extras];
3109
+ }
3110
+ const pool = [...ranked, ...extras];
3111
+ pool.sort((left, right) => compareUsageAware(config, left, right, byId));
3112
+ return pool;
3113
+ };
3114
+
3115
+ // src/loop/model-catalog/builtin.json
3116
+ var builtin_default = {
3117
+ providers: {
3118
+ claude: {
3119
+ creator: "anthropic",
3120
+ models: [
3121
+ { id: "opus", quality: "frontier", codingScore: 90 },
3122
+ { id: "sonnet", quality: "balanced", codingScore: 80 },
3123
+ { id: "haiku", quality: "fast", codingScore: 55 }
3124
+ ]
3125
+ },
3126
+ codex: {
3127
+ creator: "openai",
3128
+ models: [
3129
+ { id: "gpt-5.6-sol", quality: "frontier", codingScore: 92 },
3130
+ { id: "gpt-5.6-luna", quality: "balanced", codingScore: 78 },
3131
+ { id: "gpt-5.4", quality: "balanced", codingScore: 70 }
3132
+ ]
3133
+ },
3134
+ opencode: {
3135
+ creator: "opencode",
3136
+ models: [
3137
+ { id: "opencode-go/glm-5.3", quality: "balanced", codingScore: 72 },
3138
+ { id: "opencode-go/glm-5.3-flash", quality: "fast", codingScore: 58 }
3139
+ ]
3140
+ },
3141
+ grok: {
3142
+ creator: "xai",
3143
+ models: [
3144
+ { id: "grok-4.6", quality: "frontier", codingScore: 85 },
3145
+ { id: "grok-4.5", quality: "balanced", codingScore: 75 },
3146
+ { id: "grok-4-fast", quality: "fast", codingScore: 60 }
3147
+ ]
3148
+ }
3149
+ }
3150
+ };
3151
+
3152
+ // src/loop/model-catalog/aliases.json
3153
+ var aliases_default = {
3154
+ aliases: {
3155
+ claude: {
3156
+ "claude-opus-4": "opus",
3157
+ "claude-sonnet-4": "sonnet",
3158
+ "claude-haiku-4": "haiku",
3159
+ "opus-4": "opus",
3160
+ "sonnet-4": "sonnet",
3161
+ "haiku-4": "haiku"
3162
+ },
3163
+ codex: {
3164
+ "gpt-5.6": "gpt-5.6-sol",
3165
+ o3: "gpt-5.6-sol"
3166
+ },
3167
+ grok: {
3168
+ "grok-4": "grok-4.5",
3169
+ "grok-4-latest": "grok-4.6"
3170
+ },
3171
+ opencode: {}
3172
+ }
3173
+ };
3174
+
3175
+ // src/loop/model-catalog/index.ts
3176
+ var readJson2 = (path) => JSON.parse(readFileSync(path, "utf8"));
3177
+ var loadBuiltinCatalog = () => {
3178
+ const raw = builtin_default;
3179
+ return Object.fromEntries(Object.entries(raw.providers).map(([id2, value]) => [id2, {
3180
+ creator: value.creator,
3181
+ models: value.models.map((model) => ({ ...model, source: "builtin", creator: value.creator }))
3182
+ }]));
3183
+ };
3184
+ var loadAliases = () => aliases_default.aliases;
3185
+ var resolveAlias = (provider, modelId, aliases = loadAliases()) => aliases[provider]?.[modelId] ?? aliases[provider]?.[modelId.toLowerCase()] ?? modelId;
3186
+ var parseGrokModelsOutput = (stdout) => {
3187
+ const models = [];
3188
+ for (const line2 of stdout.split(/\r?\n/)) {
3189
+ const match = line2.match(/^\s*[-*]?\s*(grok-[a-z0-9][a-z0-9._-]*)\b/i) ?? line2.match(/^\s*\*\s*(grok-[a-z0-9][a-z0-9._-]*)\b/i);
3190
+ if (match?.[1]) models.push(match[1]);
3191
+ }
3192
+ return [...new Set(models)];
3193
+ };
3194
+ var listCliModels = async (provider, bin, runner, timeoutMs = 2e4) => {
3195
+ if (provider === "grok") {
3196
+ const outcome = await runner.run([bin, "models"], { timeoutMs });
3197
+ if (outcome.code !== 0 && !outcome.stdout.trim()) return [];
3198
+ return parseGrokModelsOutput(`${outcome.stdout}
3199
+ ${outcome.stderr}`);
3200
+ }
3201
+ return [];
3202
+ };
3203
+ var parseArtificialAnalysisPayload = (payload) => {
3204
+ const root = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
3205
+ const data = Array.isArray(root["data"]) ? root["data"] : Array.isArray(payload) ? payload : [];
3206
+ return data.flatMap((item) => {
3207
+ if (!item || typeof item !== "object" || Array.isArray(item)) return [];
3208
+ const row = item;
3209
+ const creator = row["model_creator"] && typeof row["model_creator"] === "object" && !Array.isArray(row["model_creator"]) ? row["model_creator"] : {};
3210
+ const evaluations = row["evaluations"] && typeof row["evaluations"] === "object" && !Array.isArray(row["evaluations"]) ? row["evaluations"] : {};
3211
+ const slug = typeof row["slug"] === "string" ? row["slug"] : typeof row["id"] === "string" ? row["id"] : null;
3212
+ if (!slug) return [];
3213
+ return [{
3214
+ slug,
3215
+ name: typeof row["name"] === "string" ? row["name"] : slug,
3216
+ creatorSlug: typeof creator["slug"] === "string" ? creator["slug"] : "unknown",
3217
+ codingIndex: typeof evaluations["artificial_analysis_coding_index"] === "number" ? evaluations["artificial_analysis_coding_index"] : null,
3218
+ intelligenceIndex: typeof evaluations["artificial_analysis_intelligence_index"] === "number" ? evaluations["artificial_analysis_intelligence_index"] : null
3219
+ }];
3220
+ });
3221
+ };
3222
+ var readAaCache = (stateDir) => {
3223
+ const path = join(stateDir, "catalog", "artificial-analysis.json");
3224
+ if (!existsSync(path)) return null;
3225
+ try {
3226
+ const raw = readJson2(path);
3227
+ return { fetchedAt: raw.fetchedAt, models: raw.models };
3228
+ } catch {
3229
+ return null;
3230
+ }
3231
+ };
3232
+ var writeAaCache = (stateDir, models) => {
3233
+ const path = join(stateDir, "catalog", "artificial-analysis.json");
3234
+ mkdirSync(dirname(path), { recursive: true });
3235
+ const tmp = `${path}.${process.pid}.tmp`;
3236
+ writeFileSync(tmp, `${JSON.stringify({ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), models }, null, 2)}
3237
+ `, "utf8");
3238
+ renameSync(tmp, path);
3239
+ };
3240
+ var fetchArtificialAnalysisModels = async (input) => {
3241
+ const controller = new AbortController();
3242
+ const timer = setTimeout(() => controller.abort(), input.timeoutMs ?? 2e4);
3243
+ try {
3244
+ const response = await fetch(input.endpoint, {
3245
+ headers: { "x-api-key": input.apiKey, accept: "application/json" },
3246
+ signal: controller.signal
3247
+ });
3248
+ if (!response.ok) throw new Error(`Artificial Analysis HTTP ${response.status}`);
3249
+ return parseArtificialAnalysisPayload(await response.json());
3250
+ } finally {
3251
+ clearTimeout(timer);
3252
+ }
3253
+ };
3254
+ var creatorForProvider = {
3255
+ claude: "anthropic",
3256
+ codex: "openai",
3257
+ grok: "xai",
3258
+ opencode: "opencode"
3259
+ };
3260
+ var qualityRank = { frontier: 3, balanced: 2, fast: 1 };
3261
+ var matchesQuality = (model, wanted) => {
3262
+ if (wanted === "frontier") return model.quality === "frontier" || model.codingScore >= 80;
3263
+ if (wanted === "balanced") return model.quality !== "fast" || model.codingScore >= 65;
3264
+ return true;
3265
+ };
3266
+ var resolveCatalogCandidates = async (input) => {
3267
+ const { config, role } = input;
3268
+ const policy = config.models.roles[role];
3269
+ const sources = config.models.catalog.sources;
3270
+ const builtin = loadBuiltinCatalog();
3271
+ const aliases = loadAliases();
3272
+ const byProvider = /* @__PURE__ */ new Map();
3273
+ const push = (provider, model) => {
3274
+ const list2 = byProvider.get(provider) ?? [];
3275
+ if (list2.some((item) => item.id === model.id)) return;
3276
+ list2.push(model);
3277
+ byProvider.set(provider, list2);
3278
+ };
3279
+ for (const provider of input.availableProviderIds) {
3280
+ if (sources.includes("builtin") && builtin[provider]) {
3281
+ for (const model of builtin[provider].models) push(provider, model);
3282
+ }
3283
+ if (sources.includes("cli") && input.runner) {
3284
+ const settings = config.models.providers[provider];
3285
+ if (settings) {
3286
+ try {
3287
+ const ids = await listCliModels(provider, settings.bin, input.runner);
3288
+ for (const id2 of ids) {
3289
+ const resolved = resolveAlias(provider, id2, aliases);
3290
+ const existing = builtin[provider]?.models.find((model) => model.id === resolved);
3291
+ push(provider, existing ?? { id: resolved, quality: "balanced", codingScore: 70, source: "cli", creator: creatorForProvider[provider] });
3292
+ }
3293
+ } catch {
3294
+ }
3295
+ }
3296
+ }
3297
+ }
3298
+ if (sources.includes("artificial-analysis") && config.models.catalog.artificialAnalysis.enabled && input.stateDir) {
3299
+ const aa = config.models.catalog.artificialAnalysis;
3300
+ const env = input.env ?? process.env;
3301
+ const key = env[aa.apiKeyEnv]?.trim();
3302
+ let models = readAaCache(input.stateDir);
3303
+ const now4 = input.now ?? (() => /* @__PURE__ */ new Date());
3304
+ const stale = !models || now4().getTime() - Date.parse(models.fetchedAt) > aa.cacheHours * 36e5;
3305
+ if (key && stale) {
3306
+ try {
3307
+ const fresh = await fetchArtificialAnalysisModels({ endpoint: aa.endpoint, apiKey: key });
3308
+ writeAaCache(input.stateDir, fresh);
3309
+ models = { fetchedAt: now4().toISOString(), models: fresh };
3310
+ } catch {
3311
+ }
3312
+ }
3313
+ if (models) {
3314
+ for (const provider of input.availableProviderIds) {
3315
+ const creator = creatorForProvider[provider] ?? provider;
3316
+ const matches2 = models.models.filter((model) => model.creatorSlug === creator || model.creatorSlug.includes(creator));
3317
+ for (const model of matches2) {
3318
+ const id2 = resolveAlias(provider, model.slug, aliases);
3319
+ const score = model.codingIndex ?? model.intelligenceIndex ?? 50;
3320
+ const quality = score >= 80 ? "frontier" : score >= 60 ? "balanced" : "fast";
3321
+ push(provider, { id: id2, quality, codingScore: score, source: "artificial-analysis", creator });
3322
+ }
3323
+ }
3324
+ }
3325
+ }
3326
+ const refs = [];
3327
+ for (const provider of input.availableProviderIds) {
3328
+ let models = byProvider.get(provider) ?? [];
3329
+ if (policy.preferCreators.length) {
3330
+ const preferred = models.filter((model) => model.creator && policy.preferCreators.includes(model.creator));
3331
+ if (preferred.length) models = preferred;
3332
+ }
3333
+ models = models.filter((model) => matchesQuality(model, policy.quality));
3334
+ models = [...models].sort((left, right) => {
3335
+ const qualityDelta = qualityRank[right.quality] - qualityRank[left.quality];
3336
+ if (qualityDelta) return qualityDelta;
3337
+ return right.codingScore - left.codingScore;
3338
+ });
3339
+ for (const model of models.slice(0, 3)) {
3340
+ refs.push(parseModelRef(`${provider}/${model.id}`));
3341
+ }
3342
+ }
3343
+ return refs;
2896
3344
  };
2897
3345
  var cooldownPath = (stateDir) => join(stateDir, "provider-cooldowns.json");
2898
3346
  var readCooldowns = (stateDir) => {
@@ -2963,11 +3411,31 @@ var runLoopDoctor = async (input) => {
2963
3411
  ]);
2964
3412
  const cooldowns = activeCooldowns(readCooldowns(loaded.stateDir), now4());
2965
3413
  const providers = await detectProviders({ providers: providerSpecs(config), accountList: accountList ?? {}, agentHooks, env: input.env, platform: input.platform, exhaustedPercent: config.models.cooldown.exhaustedPercent, cooldowns, now: now4, ...input.probe === false ? {} : { runner: input.runner } });
2966
- for (const provider of providers) push(`provider.${provider.id}`, provider.available ? "passed" : "warning", provider.available ? `available${provider.usage.windows.length ? ` (${provider.usage.windows.map((window) => `${window.kind} ${window.usedPercent}%`).join(", ")})` : ""}` : provider.reasons.join("; "));
2967
- const routing = routeAllRoles(config, providers);
3414
+ for (const provider of providers) {
3415
+ const remaining = remainingUsagePercent(provider.usage, config.models.routing.usageMetric);
3416
+ const usageDetail = provider.usage.windows.length ? ` (${provider.usage.windows.map((window) => `${window.kind} ${window.usedPercent}%`).join(", ")}; remaining~${remaining ?? "?"}%)` : "";
3417
+ push(`provider.${provider.id}`, provider.available ? "passed" : "warning", provider.available ? `available${usageDetail}` : provider.reasons.join("; "));
3418
+ }
3419
+ const undeclared = undeclaredOrcaProviders(accountList ?? {}, Object.fromEntries(Object.entries(config.models.providers).map(([id2, settings]) => [id2, { orcaUsageKey: settings.orcaUsageKey ?? id2 }])));
3420
+ if (undeclared.length) push("orca.undeclared-providers", "warning", `Orca shows integrations without models.providers entries: ${undeclared.join(", ")} \u2014 add a provider block (bin/tui) or ignore`);
3421
+ const extrasByRole = config.models.routing.mode === "catalog" ? Object.fromEntries(await Promise.all(MODEL_ROLES.map(async (role) => [role, await resolveCatalogCandidates({
3422
+ config,
3423
+ role,
3424
+ availableProviderIds: providers.filter((provider) => provider.available).map((provider) => provider.id),
3425
+ runner: input.runner,
3426
+ stateDir: loaded.stateDir,
3427
+ env: input.env,
3428
+ now: now4
3429
+ })]))) : {};
3430
+ const routing = routeAllRoles(config, providers, extrasByRole);
2968
3431
  for (const role of MODEL_ROLES) {
2969
3432
  const decision = routing[role];
2970
- push(`routing.${role}`, decision.selected ? "passed" : "failed", decision.selected ? `${decision.selected.provider}/${decision.selected.model} (tier ${decision.selected.tier + 1})` : `no available provider in any tier (${decision.skipped.length} skipped)`);
3433
+ const selected = decision.selected;
3434
+ push(
3435
+ `routing.${role}`,
3436
+ selected ? "passed" : "failed",
3437
+ selected ? `${selected.provider}/${selected.model} \xB7 mode ${config.models.routing.mode} \xB7 ${selected.reason}${selected.remainingPercent !== null ? ` \xB7 remaining ${selected.remainingPercent}%` : ""}` : `no available provider (${decision.skipped.length} skipped; mode ${config.models.routing.mode})`
3438
+ );
2971
3439
  }
2972
3440
  let worktrees = [];
2973
3441
  let workersError = null;
@@ -3626,7 +4094,17 @@ var gatherLoopState = async (input) => {
3626
4094
  fetchLinearQueue(input.runner, { bin: config.orca.bin, workspaceId: config.linear.workspaceId, teamKey: config.linear.teamKey, assignee: config.linear.person, filter: config.linear, orca })
3627
4095
  ]);
3628
4096
  const providers = await detectProviders({ providers: providerSpecs(config), accountList, agentHooks, env: input.env, platform: input.platform, exhaustedPercent: config.models.cooldown.exhaustedPercent, cooldowns: activeCooldowns(readCooldowns(input.loaded.stateDir), input.now()), now: input.now });
3629
- const routing = routeAllRoles(config, providers);
4097
+ const availableIds = providers.filter((provider) => provider.available).map((provider) => provider.id);
4098
+ const extrasByRole = config.models.routing.mode === "catalog" ? Object.fromEntries(await Promise.all(MODEL_ROLES.map(async (role) => [role, await resolveCatalogCandidates({
4099
+ config,
4100
+ role,
4101
+ availableProviderIds: availableIds,
4102
+ runner: input.runner,
4103
+ stateDir: input.loaded.stateDir,
4104
+ env: input.env,
4105
+ now: input.now
4106
+ })]))) : {};
4107
+ const routing = routeAllRoles(config, providers, extrasByRole);
3630
4108
  const running = countRunningWorkers(worktrees);
3631
4109
  const slots = assessSlots({ machine: config.machine, running, platform: input.platform, ...input.machine });
3632
4110
  const leases = input.ledger.active();
@@ -3668,7 +4146,16 @@ var runTick = async (input) => {
3668
4146
  const results = [];
3669
4147
  const state = await gatherLoopState({ loaded, runner: input.runner, ledger, env: input.env, platform: input.platform, now: now4, onlyIssue: input.onlyIssue, machine: input.machine });
3670
4148
  const orchestrator = state.routing["orchestrator"] ?? { role: "orchestrator", selected: null, skipped: [] };
3671
- const orchestratorCandidates = rankModels(config, "orchestrator", state.providers);
4149
+ const orchestratorExtras = config.models.routing.mode === "catalog" ? await resolveCatalogCandidates({
4150
+ config,
4151
+ role: "orchestrator",
4152
+ availableProviderIds: state.providers.filter((provider) => provider.available).map((provider) => provider.id),
4153
+ runner: input.runner,
4154
+ stateDir: loaded.stateDir,
4155
+ env: input.env,
4156
+ now: now4
4157
+ }) : [];
4158
+ const orchestratorCandidates = rankModels(config, "orchestrator", state.providers, orchestratorExtras);
3672
4159
  const onProviderFailure = (failure) => {
3673
4160
  if (dryRun) return;
3674
4161
  const entry = markProviderExhausted(loaded.stateDir, failure.provider, { initialMin: config.models.cooldown.initialMin, maxMin: config.models.cooldown.maxMin, reason: `${failure.kind}: ${(failure.detail.split("\n")[0] ?? "").slice(0, 200)}`, now: now4() });
@@ -4169,7 +4656,16 @@ var runDeliver = async (input) => {
4169
4656
  const orca = orcaOptions(config);
4170
4657
  const [accountList, agentHooks] = await Promise.all([orcaAccountList(input.runner, orca).catch(() => ({})), orcaAgentHooks(input.runner, orca).catch(() => ({}))]);
4171
4658
  const providers = await detectProviders({ providers: providerSpecs(config), accountList, agentHooks, env: input.env, platform: input.platform, exhaustedPercent: config.models.cooldown.exhaustedPercent, cooldowns: activeCooldowns(readCooldowns(loaded.stateDir), now4()), now: now4 });
4172
- const reviewer = rankModels(config, "reviewer", providers)[0] ?? null;
4659
+ const reviewerExtras = config.models.routing.mode === "catalog" ? await resolveCatalogCandidates({
4660
+ config,
4661
+ role: "reviewer",
4662
+ availableProviderIds: providers.filter((provider) => provider.available).map((provider) => provider.id),
4663
+ runner: input.runner,
4664
+ stateDir: loaded.stateDir,
4665
+ env: input.env,
4666
+ now: now4
4667
+ }) : [];
4668
+ const reviewer = rankModels(config, "reviewer", providers, reviewerExtras)[0] ?? null;
4173
4669
  let env = input.env ?? process.env;
4174
4670
  if (!env["GITHUB_TOKEN"] && !env["GH_TOKEN"]) {
4175
4671
  try {