@agentskit/harness 0.6.0 → 0.8.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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { resolve, dirname, join, relative, basename, isAbsolute, delimiter, extname, sep } from 'path';
1
+ import { resolve, dirname, join, relative, isAbsolute, delimiter, basename, extname, sep } from 'path';
2
2
  import { createHash, randomUUID, createPrivateKey, createPublicKey, sign, verify } from 'crypto';
3
3
  import { existsSync, mkdirSync, openSync, writeSync, appendFileSync, closeSync, unlinkSync, readFileSync, writeFileSync, statSync, readdirSync, mkdtempSync, renameSync, rmSync } from 'fs';
4
4
  import { execFile, spawn, execFileSync } from 'child_process';
@@ -1266,15 +1266,174 @@ var createDocBridgeContextProvider = ({ root, indexPath = ".doc-bridge/index.jso
1266
1266
  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 };
1267
1267
  }
1268
1268
  });
1269
+ var executable = (path) => {
1270
+ try {
1271
+ return statSync(path).isFile();
1272
+ } catch {
1273
+ return false;
1274
+ }
1275
+ };
1276
+ var findExecutable = (name2, env = process.env, platform = process.platform) => {
1277
+ if (typeof name2 !== "string" || !name2.trim()) return null;
1278
+ if (isAbsolute(name2) || name2.includes("/") || name2.includes("\\")) return existsSync(name2) && executable(name2) ? name2 : null;
1279
+ const extensions = platform === "win32" ? (env["PATHEXT"] ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean) : [""];
1280
+ for (const dir of (env["PATH"] ?? "").split(delimiter).filter(Boolean)) {
1281
+ for (const extension of extensions) {
1282
+ const candidate = join(dir, `${name2}${extension}`);
1283
+ if (executable(candidate)) return candidate;
1284
+ }
1285
+ if (platform === "win32" && executable(join(dir, name2))) return join(dir, name2);
1286
+ }
1287
+ return null;
1288
+ };
1289
+ var parseJsonEnvelope = (stdout) => {
1290
+ const trimmed = stdout.trim();
1291
+ if (!trimmed) return null;
1292
+ let parsed;
1293
+ try {
1294
+ parsed = JSON.parse(trimmed);
1295
+ } catch {
1296
+ return null;
1297
+ }
1298
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
1299
+ const record3 = parsed;
1300
+ if (typeof record3.ok !== "boolean") return null;
1301
+ 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;
1302
+ return { ok: record3.ok, result: record3.result, ...error === void 0 ? {} : { error } };
1303
+ };
1269
1304
 
1270
- // src/adapters/rag-context.ts
1305
+ // src/adapters/providers.ts
1271
1306
  var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1307
+ 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;
1308
+ var parseUsageWindows = (entry) => {
1309
+ if (!isRecord5(entry)) return [];
1310
+ return Object.entries(entry).flatMap(([kind, value]) => {
1311
+ if (!isRecord5(value) || typeof value["usedPercent"] !== "number") return [];
1312
+ return [{ kind, usedPercent: value["usedPercent"], windowMinutes: typeof value["windowMinutes"] === "number" ? value["windowMinutes"] : null, resetsAt: iso(value["resetsAt"]) }];
1313
+ });
1314
+ };
1315
+ var parseProviderUsage = (accountList, usageKey, exhaustedPercent = 100) => {
1316
+ const result = isRecord5(accountList) ? accountList : {};
1317
+ const rateLimits = isRecord5(result["rateLimits"]) ? result["rateLimits"] : {};
1318
+ const entry = isRecord5(rateLimits[usageKey]) ? rateLimits[usageKey] : null;
1319
+ const account = isRecord5(result[usageKey]) ? result[usageKey] : null;
1320
+ const systemDefault = account && isRecord5(account["systemDefault"]) ? account["systemDefault"] : null;
1321
+ const accounts = account && Array.isArray(account["accounts"]) ? account["accounts"] : [];
1322
+ const hasAuth = systemDefault ? systemDefault["hasAuth"] === true : accounts.length ? true : null;
1323
+ if (!entry) return { status: "unknown", error: null, windows: [], exhausted: false, resetsAt: null, hasAuth };
1324
+ const windows = parseUsageWindows(entry);
1325
+ const exhaustedWindows = windows.filter((window) => window.usedPercent >= exhaustedPercent);
1326
+ const resetsAt = exhaustedWindows.map((window) => window.resetsAt).filter((value) => Boolean(value)).sort()[0] ?? null;
1327
+ return {
1328
+ status: entry["status"] === "ok" ? "ok" : entry["status"] === "unavailable" ? "unavailable" : "unknown",
1329
+ error: typeof entry["error"] === "string" ? entry["error"] : null,
1330
+ windows,
1331
+ exhausted: exhaustedWindows.length > 0,
1332
+ resetsAt,
1333
+ hasAuth
1334
+ };
1335
+ };
1336
+ var authStatusFor = (spec, usage2, env) => {
1337
+ const hasEnvKey = spec.envKeys.some((key) => Boolean(env[key]?.trim()));
1338
+ if (spec.auth === "api-key") return hasEnvKey ? "ok" : "missing";
1339
+ if (spec.auth === "subscription") return usage2.hasAuth === true || usage2.status === "ok" ? "ok" : usage2.hasAuth === false ? "missing" : hasEnvKey || usage2.status === "unknown" ? "ok" : "unknown";
1340
+ return hasEnvKey || usage2.status === "ok" ? "ok" : "unknown";
1341
+ };
1342
+ var runProbe = async (spec, binary, runner, timeoutMs) => {
1343
+ if (!spec.probe || !runner) return "skipped";
1344
+ const [head, ...rest] = spec.probe;
1345
+ const argv = [head === spec.bin ? binary : head ?? binary, ...rest];
1346
+ try {
1347
+ const outcome = await runner.run(argv, { timeoutMs });
1348
+ return outcome.code === 0 && !outcome.timedOut ? "passed" : "failed";
1349
+ } catch {
1350
+ return "failed";
1351
+ }
1352
+ };
1353
+ var detectProviders = async (input) => {
1354
+ const env = input.env ?? process.env;
1355
+ const platform = input.platform ?? process.platform;
1356
+ const now4 = input.now ?? (() => /* @__PURE__ */ new Date());
1357
+ const results = [];
1358
+ for (const spec of input.providers) {
1359
+ const binary = findExecutable(spec.bin, env, platform);
1360
+ const hookState = input.agentHooks[spec.id] ?? "unknown";
1361
+ const usage2 = parseProviderUsage(input.accountList, spec.orcaUsageKey, input.exhaustedPercent ?? 100);
1362
+ const auth = authStatusFor(spec, usage2, env);
1363
+ const cooldown = input.cooldowns?.[spec.id] ?? null;
1364
+ const coolingDownUntil = cooldown && Date.parse(cooldown) > now4().getTime() ? new Date(cooldown).toISOString() : null;
1365
+ const reasons = [];
1366
+ if (!binary) reasons.push(`binary "${spec.bin}" not found on PATH`);
1367
+ 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`);
1368
+ if (usage2.exhausted) reasons.push(`usage exhausted${usage2.resetsAt ? ` until ${usage2.resetsAt}` : ""}`);
1369
+ if (coolingDownUntil) reasons.push(`cooling down until ${coolingDownUntil}`);
1370
+ const probe = binary && !reasons.length ? await runProbe(spec, binary, input.runner, input.probeTimeoutMs ?? 15e3) : "skipped";
1371
+ if (probe === "failed") reasons.push("probe command failed");
1372
+ results.push({ id: spec.id, binary, hookState, auth, usage: usage2, probe, coolingDownUntil, available: reasons.length === 0, reasons });
1373
+ }
1374
+ return results;
1375
+ };
1376
+ var cooldownUntil = (attempt, initialMin, maxMin, from, resetsAt = null) => {
1377
+ const minutes2 = Math.min(maxMin, initialMin * 2 ** Math.max(0, attempt));
1378
+ const backoff = from.getTime() + minutes2 * 6e4;
1379
+ const reset = resetsAt ? Date.parse(resetsAt) : Number.NaN;
1380
+ return new Date(Number.isFinite(reset) && reset > from.getTime() ? Math.max(reset, backoff) : backoff).toISOString();
1381
+ };
1382
+ var remainingUsagePercent = (usage2, metric = "max") => {
1383
+ if (usage2.status !== "ok" || !usage2.windows.length) return null;
1384
+ const windows = metric === "max" ? usage2.windows : usage2.windows.filter((window) => window.kind === metric);
1385
+ const pool = windows.length ? windows : usage2.windows;
1386
+ if (!pool.length) return null;
1387
+ const worst = Math.max(...pool.map((window) => window.usedPercent));
1388
+ return Math.max(0, Math.min(100, 100 - worst));
1389
+ };
1390
+ var usageRankTuple = (usage2, metric, preferKnownUsage) => {
1391
+ const remaining = remainingUsagePercent(usage2, metric);
1392
+ const known = remaining === null ? 1 : 0;
1393
+ const remainingKey = remaining === null ? 0 : -remaining;
1394
+ const resetMs = usage2.resetsAt ? Date.parse(usage2.resetsAt) : Number.POSITIVE_INFINITY;
1395
+ return preferKnownUsage ? [known, remainingKey, resetMs] : [remainingKey, known, resetMs];
1396
+ };
1397
+ var ORCA_META_KEYS = /* @__PURE__ */ new Set([
1398
+ "minimaxCookieConfigured",
1399
+ "minimaxApiKeyConfigured",
1400
+ "grokAuthConfigured",
1401
+ "claudeTarget",
1402
+ "codexTarget",
1403
+ "inactiveClaudeAccounts",
1404
+ "inactiveCodexAccounts"
1405
+ ]);
1406
+ var listOrcaIntegratedProviderKeys = (accountList) => {
1407
+ const result = isRecord5(accountList) ? accountList : {};
1408
+ const rateLimits = isRecord5(result["rateLimits"]) ? result["rateLimits"] : {};
1409
+ const keys = /* @__PURE__ */ new Set();
1410
+ for (const key of Object.keys(rateLimits)) {
1411
+ if (!ORCA_META_KEYS.has(key) && isRecord5(rateLimits[key])) keys.add(key);
1412
+ }
1413
+ for (const key of Object.keys(result)) {
1414
+ if (key === "rateLimits" || ORCA_META_KEYS.has(key)) continue;
1415
+ if (isRecord5(result[key])) keys.add(key);
1416
+ }
1417
+ return [...keys].sort();
1418
+ };
1419
+ var undeclaredOrcaProviders = (accountList, declared) => {
1420
+ const usageKeyToId = /* @__PURE__ */ new Map();
1421
+ for (const [id2, settings] of Object.entries(declared)) {
1422
+ usageKeyToId.set(settings.orcaUsageKey ?? id2, id2);
1423
+ usageKeyToId.set(id2, id2);
1424
+ }
1425
+ usageKeyToId.set("opencodeGo", usageKeyToId.get("opencodeGo") ?? "opencode");
1426
+ return listOrcaIntegratedProviderKeys(accountList).filter((key) => !usageKeyToId.has(key));
1427
+ };
1428
+
1429
+ // src/adapters/rag-context.ts
1430
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1272
1431
  var requiredString2 = (value, label) => {
1273
1432
  if (typeof value !== "string" || !value.trim()) return fail(`${label} must be a non-empty string.`, "INVALID_INPUT");
1274
1433
  return value;
1275
1434
  };
1276
1435
  var parseReference = (value, index2) => {
1277
- if (!isRecord5(value)) return fail(`RAG references[${index2}] must be an object.`, "INVALID_INPUT");
1436
+ if (!isRecord6(value)) return fail(`RAG references[${index2}] must be an object.`, "INVALID_INPUT");
1278
1437
  const relevance = value["relevance"];
1279
1438
  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");
1280
1439
  return {
@@ -1287,7 +1446,7 @@ var parseReference = (value, index2) => {
1287
1446
  };
1288
1447
  };
1289
1448
  var parseRagQueryOutput = (value) => {
1290
- if (!isRecord5(value)) return fail("RAG query output must be a JSON object.", "INVALID_INPUT");
1449
+ if (!isRecord6(value)) return fail("RAG query output must be a JSON object.", "INVALID_INPUT");
1291
1450
  const rawReferences = value["references"];
1292
1451
  if (!Array.isArray(rawReferences)) return fail("RAG query output.references must be an array.", "INVALID_INPUT");
1293
1452
  const references = rawReferences.map((entry, index2) => parseReference(entry, index2));
@@ -2361,7 +2520,7 @@ var artifactId = (value) => {
2361
2520
  if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(result)) fail("Artifact artifactId is invalid.", "INVALID_INPUT");
2362
2521
  return result;
2363
2522
  };
2364
- var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2523
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2365
2524
  var artifactBody = (artifact) => ({
2366
2525
  type: artifact.type,
2367
2526
  schemaVersion: artifact.schemaVersion,
@@ -2380,7 +2539,7 @@ var artifactBody = (artifact) => ({
2380
2539
  });
2381
2540
  var expectedArtifactHash = (artifact) => hashJson(artifactBody(artifact));
2382
2541
  var validateArtifactEnvelope = (value) => {
2383
- if (!isRecord6(value)) return fail("Artifact envelope must be an object.", "INVALID_INPUT");
2542
+ if (!isRecord7(value)) return fail("Artifact envelope must be an object.", "INVALID_INPUT");
2384
2543
  if (value["type"] !== "agentskit-harness-artifact" || value["schemaVersion"] !== ARTIFACT_SCHEMA_VERSION) fail("Artifact envelope type or schemaVersion is invalid.", "INVALID_INPUT");
2385
2544
  if (!ARTIFACT_TYPES.includes(value["artifactType"])) fail("Artifact artifactType is invalid.", "INVALID_INPUT");
2386
2545
  if (!Number.isInteger(value["artifactVersion"]) || value["artifactVersion"] < 1) fail("Artifact artifactVersion must be a positive integer.", "INVALID_INPUT");
@@ -2499,8 +2658,8 @@ var resumeStateFromArtifacts = (artifacts) => {
2499
2658
  const completed = {};
2500
2659
  const outputs = {};
2501
2660
  for (const artifact of artifacts.filter((item) => item.artifactType === "phase").sort((left, right) => left.phase.localeCompare(right.phase) || left.artifactVersion - right.artifactVersion)) {
2502
- if (!isRecord6(artifact.payload) || artifact.payload["decision"] !== "pass") continue;
2503
- const phaseOutputs = isRecord6(artifact.payload["outputs"]) ? artifact.payload["outputs"] : {};
2661
+ if (!isRecord7(artifact.payload) || artifact.payload["decision"] !== "pass") continue;
2662
+ const phaseOutputs = isRecord7(artifact.payload["outputs"]) ? artifact.payload["outputs"] : {};
2504
2663
  completed[artifact.phase] = { decision: "pass", outputs: phaseOutputs };
2505
2664
  Object.assign(outputs, phaseOutputs);
2506
2665
  }
@@ -3908,44 +4067,9 @@ var readEvidenceTrustStore = (path) => {
3908
4067
  return key;
3909
4068
  });
3910
4069
  };
3911
- var executable = (path) => {
3912
- try {
3913
- return statSync(path).isFile();
3914
- } catch {
3915
- return false;
3916
- }
3917
- };
3918
- var findExecutable = (name2, env = process.env, platform = process.platform) => {
3919
- if (typeof name2 !== "string" || !name2.trim()) return null;
3920
- if (isAbsolute(name2) || name2.includes("/") || name2.includes("\\")) return existsSync(name2) && executable(name2) ? name2 : null;
3921
- const extensions = platform === "win32" ? (env["PATHEXT"] ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean) : [""];
3922
- for (const dir of (env["PATH"] ?? "").split(delimiter).filter(Boolean)) {
3923
- for (const extension of extensions) {
3924
- const candidate = join(dir, `${name2}${extension}`);
3925
- if (executable(candidate)) return candidate;
3926
- }
3927
- if (platform === "win32" && executable(join(dir, name2))) return join(dir, name2);
3928
- }
3929
- return null;
3930
- };
3931
- var parseJsonEnvelope = (stdout) => {
3932
- const trimmed = stdout.trim();
3933
- if (!trimmed) return null;
3934
- let parsed;
3935
- try {
3936
- parsed = JSON.parse(trimmed);
3937
- } catch {
3938
- return null;
3939
- }
3940
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
3941
- const record3 = parsed;
3942
- if (typeof record3.ok !== "boolean") return null;
3943
- 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;
3944
- return { ok: record3.ok, result: record3.result, ...error === void 0 ? {} : { error } };
3945
- };
3946
4070
 
3947
4071
  // src/adapters/orca-cli.ts
3948
- var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4072
+ var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3949
4073
  var str = (value, fallback = "") => typeof value === "string" ? value : fallback;
3950
4074
  var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
3951
4075
  var compareVersions = (left, right) => {
@@ -3959,9 +4083,9 @@ var compareVersions = (left, right) => {
3959
4083
  };
3960
4084
  var parseOrcaVersion = (stdout) => stdout.match(/\d+\.\d+\.\d+/)?.[0] ?? null;
3961
4085
  var parseOrcaStatus = (result) => {
3962
- const record3 = isRecord7(result) ? result : {};
3963
- const app = isRecord7(record3["app"]) ? record3["app"] : {};
3964
- const runtime = isRecord7(record3["runtime"]) ? record3["runtime"] : {};
4086
+ const record3 = isRecord8(result) ? result : {};
4087
+ const app = isRecord8(record3["app"]) ? record3["app"] : {};
4088
+ const runtime = isRecord8(record3["runtime"]) ? record3["runtime"] : {};
3965
4089
  return {
3966
4090
  appRunning: app["running"] === true,
3967
4091
  runtimeReady: runtime["state"] === "ready" && runtime["reachable"] === true,
@@ -3972,14 +4096,14 @@ var parseOrcaStatus = (result) => {
3972
4096
  };
3973
4097
  var linkedLinear = (value) => {
3974
4098
  if (typeof value === "string" && value.trim()) return value.trim();
3975
- if (isRecord7(value)) {
4099
+ if (isRecord8(value)) {
3976
4100
  for (const key of ["identifier", "id", "url"]) if (typeof value[key] === "string" && value[key].trim()) return value[key].trim();
3977
4101
  }
3978
4102
  return null;
3979
4103
  };
3980
4104
  var parseOrcaWorktrees = (result) => {
3981
- const list2 = isRecord7(result) && Array.isArray(result["worktrees"]) ? result["worktrees"] : Array.isArray(result) ? result : [];
3982
- return list2.filter(isRecord7).map((item) => ({
4105
+ const list2 = isRecord8(result) && Array.isArray(result["worktrees"]) ? result["worktrees"] : Array.isArray(result) ? result : [];
4106
+ return list2.filter(isRecord8).map((item) => ({
3983
4107
  id: str(item["worktreeId"], str(item["id"])),
3984
4108
  repoId: str(item["repoId"]),
3985
4109
  repo: str(item["repo"]),
@@ -3996,8 +4120,8 @@ var parseOrcaWorktrees = (result) => {
3996
4120
  })).filter((item) => item.id);
3997
4121
  };
3998
4122
  var parseOrcaAgentHooks = (result) => {
3999
- const statuses = isRecord7(result) && Array.isArray(result["statuses"]) ? result["statuses"] : [];
4000
- return Object.fromEntries(statuses.filter(isRecord7).flatMap((item) => {
4123
+ const statuses = isRecord8(result) && Array.isArray(result["statuses"]) ? result["statuses"] : [];
4124
+ return Object.fromEntries(statuses.filter(isRecord8).flatMap((item) => {
4001
4125
  const agent = str(item["agent"]);
4002
4126
  if (!agent) return [];
4003
4127
  const state = item["state"] === "installed" ? "installed" : item["state"] === "not_installed" ? "not_installed" : "unknown";
@@ -4023,9 +4147,9 @@ var orcaWorktrees = async (runner, options = {}) => parseOrcaWorktrees(await orc
4023
4147
  var orcaAgentHooks = async (runner, options = {}) => parseOrcaAgentHooks(await orcaJson(runner, ["agent", "hooks", "status"], options));
4024
4148
  var orcaAccountList = async (runner, options = {}) => orcaJson(runner, ["account", "list"], options);
4025
4149
  var parseOrcaWorktreeCreate = (result) => {
4026
- const record3 = isRecord7(result) ? result : {};
4027
- const nested = isRecord7(record3["worktree"]) ? record3["worktree"] : record3;
4028
- const startup = isRecord7(record3["startupTerminal"]) ? record3["startupTerminal"] : isRecord7(nested["startupTerminal"]) ? nested["startupTerminal"] : {};
4150
+ const record3 = isRecord8(result) ? result : {};
4151
+ const nested = isRecord8(record3["worktree"]) ? record3["worktree"] : record3;
4152
+ const startup = isRecord8(record3["startupTerminal"]) ? record3["startupTerminal"] : isRecord8(nested["startupTerminal"]) ? nested["startupTerminal"] : {};
4029
4153
  const id2 = str(nested["worktreeId"], str(nested["id"], str(record3["worktreeId"], str(record3["id"]))));
4030
4154
  if (!id2) fail("orca worktree create returned no worktree id.", "HARNESS_ERROR");
4031
4155
  return {
@@ -4055,8 +4179,8 @@ var orcaWorktreeSetArgv = (input, bin = "orca") => [
4055
4179
  var orcaWorktreeSet = async (runner, input, options = {}) => orcaJson(runner, orcaWorktreeSetArgv(input).slice(1), options);
4056
4180
  var orcaWorktreeRemove = async (runner, input, options = {}) => orcaJson(runner, ["worktree", "rm", "--worktree", input.worktree, ...input.force ? ["--force"] : []], { ...options, timeoutMs: options.timeoutMs ?? 6e4 });
4057
4181
  var parseOrcaTerminals = (result) => {
4058
- const list2 = isRecord7(result) ? Array.isArray(result["terminals"]) ? result["terminals"] : Array.isArray(result["items"]) ? result["items"] : [] : Array.isArray(result) ? result : [];
4059
- return list2.filter(isRecord7).map((item) => ({
4182
+ const list2 = isRecord8(result) ? Array.isArray(result["terminals"]) ? result["terminals"] : Array.isArray(result["items"]) ? result["items"] : [] : Array.isArray(result) ? result : [];
4183
+ return list2.filter(isRecord8).map((item) => ({
4060
4184
  handle: str(item["handle"], str(item["id"])),
4061
4185
  title: str(item["title"], str(item["name"])),
4062
4186
  worktreeId: str(item["worktreeId"], str(item["worktree"])) || null,
@@ -4071,35 +4195,35 @@ var parseOrcaTerminals = (result) => {
4071
4195
  var orcaTerminalList = async (runner, input = {}, options = {}) => parseOrcaTerminals(await orcaJson(runner, ["terminal", "list", ...input.worktree ? ["--worktree", input.worktree] : [], ...input.limit ? ["--limit", String(input.limit)] : []], options));
4072
4196
  var orcaTerminalCreate = async (runner, input, options = {}) => {
4073
4197
  const result = await orcaJson(runner, ["terminal", "create", "--worktree", input.worktree, "--command", input.command, ...input.title ? ["--title", input.title] : []], { ...options, timeoutMs: options.timeoutMs ?? 6e4 });
4074
- const record3 = isRecord7(result) ? result : {};
4075
- const terminal2 = isRecord7(record3["terminal"]) ? record3["terminal"] : record3;
4198
+ const record3 = isRecord8(result) ? result : {};
4199
+ const terminal2 = isRecord8(record3["terminal"]) ? record3["terminal"] : record3;
4076
4200
  const handle = str(terminal2["handle"], str(record3["handle"]));
4077
4201
  if (!handle) fail("orca terminal create returned no terminal handle.", "HARNESS_ERROR");
4078
4202
  return { handle, raw: result };
4079
4203
  };
4080
4204
  var parseOrcaSendReceipt = (result) => {
4081
- const record3 = isRecord7(result) ? result : {};
4082
- const receipt = isRecord7(record3["receipt"]) ? record3["receipt"] : record3;
4083
- const stages = Array.isArray(receipt["stages"]) ? receipt["stages"].map((stage) => isRecord7(stage) ? str(stage["stage"], str(stage["name"])) : str(stage)).filter(Boolean) : [];
4205
+ const record3 = isRecord8(result) ? result : {};
4206
+ const receipt = isRecord8(record3["receipt"]) ? record3["receipt"] : record3;
4207
+ const stages = Array.isArray(receipt["stages"]) ? receipt["stages"].map((stage) => isRecord8(stage) ? str(stage["stage"], str(stage["name"])) : str(stage)).filter(Boolean) : [];
4084
4208
  const accepted = receipt["accepted"] === false ? false : receipt["accepted"] === true || stages.includes("input_accepted") || (result === null || result === void 0 || Object.keys(record3).length === 0);
4085
- 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)) : [] };
4209
+ return { accepted, requestId: str(receipt["requestId"], str(record3["requestId"])) || null, stages, warnings: Array.isArray(record3["warnings"]) ? record3["warnings"].map((warning) => isRecord8(warning) ? str(warning["message"], JSON.stringify(warning)) : str(warning)) : [] };
4086
4210
  };
4087
4211
  var orcaTerminalSend = async (runner, input, options = {}) => parseOrcaSendReceipt(await orcaJson(runner, ["terminal", "send", "--terminal", input.terminal, "--text", input.text, ...input.enter === false ? [] : ["--enter"], ...input.waitSubmitSeconds ? ["--wait-submit", String(input.waitSubmitSeconds)] : []], { ...options, timeoutMs: options.timeoutMs ?? (input.waitSubmitSeconds ?? 0) * 1e3 + 3e4 }));
4088
4212
  var orcaTerminalWait = async (runner, input, options = {}) => {
4089
4213
  const result = await orcaJson(runner, ["terminal", "wait", "--terminal", input.terminal, "--for", input.for, "--timeout-ms", String(input.timeoutMs)], { ...options, timeoutMs: input.timeoutMs + 15e3 });
4090
- const record3 = isRecord7(result) ? result : {};
4091
- const wait2 = isRecord7(record3["wait"]) ? record3["wait"] : record3;
4214
+ const record3 = isRecord8(result) ? result : {};
4215
+ const wait2 = isRecord8(record3["wait"]) ? record3["wait"] : record3;
4092
4216
  return { satisfied: wait2["satisfied"] === true, raw: result };
4093
4217
  };
4094
4218
  var orcaTerminalScreen = async (runner, input, options = {}) => {
4095
4219
  const result = await orcaJson(runner, ["terminal", "read", "--terminal", input.terminal, "--screen"], options);
4096
- const record3 = isRecord7(result) ? isRecord7(result["terminal"]) ? result["terminal"] : result : {};
4220
+ const record3 = isRecord8(result) ? isRecord8(result["terminal"]) ? result["terminal"] : result : {};
4097
4221
  const screen = record3["tail"] ?? record3["screen"] ?? record3["lines"] ?? record3["text"] ?? record3["output"];
4098
- return Array.isArray(screen) ? screen.map((line2) => isRecord7(line2) ? str(line2["text"], str(line2["line"])) : String(line2)).join("\n") : typeof screen === "string" ? screen : "";
4222
+ return Array.isArray(screen) ? screen.map((line2) => isRecord8(line2) ? str(line2["text"], str(line2["line"])) : String(line2)).join("\n") : typeof screen === "string" ? screen : "";
4099
4223
  };
4100
4224
  var parseOrcaAutomations = (result) => {
4101
- const list2 = isRecord7(result) ? Array.isArray(result["automations"]) ? result["automations"] : Array.isArray(result["items"]) ? result["items"] : [] : Array.isArray(result) ? result : [];
4102
- 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);
4225
+ const list2 = isRecord8(result) ? Array.isArray(result["automations"]) ? result["automations"] : Array.isArray(result["items"]) ? result["items"] : [] : Array.isArray(result) ? result : [];
4226
+ return list2.filter(isRecord8).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);
4103
4227
  };
4104
4228
  var orcaAutomationsList = async (runner, options = {}) => parseOrcaAutomations(await orcaJson(runner, ["automations", "list"], options));
4105
4229
  var orcaAutomationCreateArgv = (spec, bin = "orca") => [
@@ -4147,84 +4271,6 @@ var orcaAutomationRemove = async (runner, id2, options = {}) => orcaJson(runner,
4147
4271
  var orcaAutomationRun = async (runner, id2, options = {}) => orcaJson(runner, ["automations", "run", id2], options);
4148
4272
  var orcaAutomationRuns = async (runner, id2, options = {}) => orcaJson(runner, ["automations", "runs", "--id", id2], options);
4149
4273
 
4150
- // src/adapters/providers.ts
4151
- var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4152
- 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;
4153
- var parseUsageWindows = (entry) => {
4154
- if (!isRecord8(entry)) return [];
4155
- return Object.entries(entry).flatMap(([kind, value]) => {
4156
- if (!isRecord8(value) || typeof value["usedPercent"] !== "number") return [];
4157
- return [{ kind, usedPercent: value["usedPercent"], windowMinutes: typeof value["windowMinutes"] === "number" ? value["windowMinutes"] : null, resetsAt: iso(value["resetsAt"]) }];
4158
- });
4159
- };
4160
- var parseProviderUsage = (accountList, usageKey, exhaustedPercent = 100) => {
4161
- const result = isRecord8(accountList) ? accountList : {};
4162
- const rateLimits = isRecord8(result["rateLimits"]) ? result["rateLimits"] : {};
4163
- const entry = isRecord8(rateLimits[usageKey]) ? rateLimits[usageKey] : null;
4164
- const account = isRecord8(result[usageKey]) ? result[usageKey] : null;
4165
- const systemDefault = account && isRecord8(account["systemDefault"]) ? account["systemDefault"] : null;
4166
- const accounts = account && Array.isArray(account["accounts"]) ? account["accounts"] : [];
4167
- const hasAuth = systemDefault ? systemDefault["hasAuth"] === true : accounts.length ? true : null;
4168
- if (!entry) return { status: "unknown", error: null, windows: [], exhausted: false, resetsAt: null, hasAuth };
4169
- const windows = parseUsageWindows(entry);
4170
- const exhaustedWindows = windows.filter((window) => window.usedPercent >= exhaustedPercent);
4171
- const resetsAt = exhaustedWindows.map((window) => window.resetsAt).filter((value) => Boolean(value)).sort()[0] ?? null;
4172
- return {
4173
- status: entry["status"] === "ok" ? "ok" : entry["status"] === "unavailable" ? "unavailable" : "unknown",
4174
- error: typeof entry["error"] === "string" ? entry["error"] : null,
4175
- windows,
4176
- exhausted: exhaustedWindows.length > 0,
4177
- resetsAt,
4178
- hasAuth
4179
- };
4180
- };
4181
- var authStatusFor = (spec, usage2, env) => {
4182
- const hasEnvKey = spec.envKeys.some((key) => Boolean(env[key]?.trim()));
4183
- if (spec.auth === "api-key") return hasEnvKey ? "ok" : "missing";
4184
- if (spec.auth === "subscription") return usage2.hasAuth === true || usage2.status === "ok" ? "ok" : usage2.hasAuth === false ? "missing" : hasEnvKey || usage2.status === "unknown" ? "ok" : "unknown";
4185
- return hasEnvKey || usage2.status === "ok" ? "ok" : "unknown";
4186
- };
4187
- var runProbe = async (spec, binary, runner, timeoutMs) => {
4188
- if (!spec.probe || !runner) return "skipped";
4189
- const [head, ...rest] = spec.probe;
4190
- const argv = [head === spec.bin ? binary : head ?? binary, ...rest];
4191
- try {
4192
- const outcome = await runner.run(argv, { timeoutMs });
4193
- return outcome.code === 0 && !outcome.timedOut ? "passed" : "failed";
4194
- } catch {
4195
- return "failed";
4196
- }
4197
- };
4198
- var detectProviders = async (input) => {
4199
- const env = input.env ?? process.env;
4200
- const platform = input.platform ?? process.platform;
4201
- const now4 = input.now ?? (() => /* @__PURE__ */ new Date());
4202
- const results = [];
4203
- for (const spec of input.providers) {
4204
- const binary = findExecutable(spec.bin, env, platform);
4205
- const hookState = input.agentHooks[spec.id] ?? "unknown";
4206
- const usage2 = parseProviderUsage(input.accountList, spec.orcaUsageKey, input.exhaustedPercent ?? 100);
4207
- const auth = authStatusFor(spec, usage2, env);
4208
- const cooldown = input.cooldowns?.[spec.id] ?? null;
4209
- const coolingDownUntil = cooldown && Date.parse(cooldown) > now4().getTime() ? new Date(cooldown).toISOString() : null;
4210
- const reasons = [];
4211
- if (!binary) reasons.push(`binary "${spec.bin}" not found on PATH`);
4212
- 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`);
4213
- if (usage2.exhausted) reasons.push(`usage exhausted${usage2.resetsAt ? ` until ${usage2.resetsAt}` : ""}`);
4214
- if (coolingDownUntil) reasons.push(`cooling down until ${coolingDownUntil}`);
4215
- const probe = binary && !reasons.length ? await runProbe(spec, binary, input.runner, input.probeTimeoutMs ?? 15e3) : "skipped";
4216
- if (probe === "failed") reasons.push("probe command failed");
4217
- results.push({ id: spec.id, binary, hookState, auth, usage: usage2, probe, coolingDownUntil, available: reasons.length === 0, reasons });
4218
- }
4219
- return results;
4220
- };
4221
- var cooldownUntil = (attempt, initialMin, maxMin, from, resetsAt = null) => {
4222
- const minutes2 = Math.min(maxMin, initialMin * 2 ** Math.max(0, attempt));
4223
- const backoff = from.getTime() + minutes2 * 6e4;
4224
- const reset = resetsAt ? Date.parse(resetsAt) : Number.NaN;
4225
- return new Date(Number.isFinite(reset) && reset > from.getTime() ? Math.max(reset, backoff) : backoff).toISOString();
4226
- };
4227
-
4228
4274
  // src/adapters/linear-orca.ts
4229
4275
  var isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4230
4276
  var str2 = (value, fallback = "") => typeof value === "string" ? value : fallback;
@@ -4376,6 +4422,41 @@ var LoopConfigSchema = z.object({
4376
4422
  reviewer: tiers,
4377
4423
  builder: tiers,
4378
4424
  watcher: tiers,
4425
+ /** 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. */
4426
+ routing: z.object({
4427
+ mode: z.enum(["tiers", "hybrid", "dynamic", "catalog"]).default("tiers"),
4428
+ /** Which usage window drives remaining%. `max` = most constrained window. */
4429
+ usageMetric: z.enum(["max", "session", "weekly", "monthly"]).default("max"),
4430
+ /** Prefer providers with live usage % over those with unknown usage (e.g. grok often has no %). */
4431
+ preferKnownUsage: z.boolean().default(true),
4432
+ excludeProviders: z.array(nonEmpty5).default([]),
4433
+ /** If non-empty, only these providers may be selected (still must be declared under providers). */
4434
+ includeProviders: z.array(nonEmpty5).default([]),
4435
+ /** Hard pin per role (`provider/model`). If pinned provider is unavailable, fall through unless pinStrict. */
4436
+ pin: z.object({
4437
+ orchestrator: modelRef.optional(),
4438
+ reviewer: modelRef.optional(),
4439
+ builder: modelRef.optional(),
4440
+ watcher: modelRef.optional()
4441
+ }).prefault({}),
4442
+ pinStrict: z.boolean().default(false)
4443
+ }).prefault({}),
4444
+ /** Quality band when `routing.mode: catalog` (and as soft bias in hybrid). */
4445
+ roles: z.object({
4446
+ orchestrator: z.object({ quality: z.enum(["frontier", "balanced", "fast"]).default("frontier"), preferCreators: z.array(nonEmpty5).default([]) }).prefault({}),
4447
+ reviewer: z.object({ quality: z.enum(["frontier", "balanced", "fast"]).default("frontier"), preferCreators: z.array(nonEmpty5).default([]) }).prefault({}),
4448
+ builder: z.object({ quality: z.enum(["frontier", "balanced", "fast"]).default("balanced"), preferCreators: z.array(nonEmpty5).default([]) }).prefault({}),
4449
+ watcher: z.object({ quality: z.enum(["frontier", "balanced", "fast"]).default("fast"), preferCreators: z.array(nonEmpty5).default([]) }).prefault({})
4450
+ }).prefault({}),
4451
+ catalog: z.object({
4452
+ sources: z.array(z.enum(["cli", "artificial-analysis", "builtin"])).default(["cli", "builtin"]),
4453
+ artificialAnalysis: z.object({
4454
+ enabled: z.boolean().default(false),
4455
+ apiKeyEnv: nonEmpty5.default("ARTIFICIAL_ANALYSIS_API_KEY"),
4456
+ cacheHours: z.number().positive().default(24),
4457
+ endpoint: nonEmpty5.default("https://artificialanalysis.ai/api/v2/data/llms/models")
4458
+ }).prefault({})
4459
+ }).prefault({}),
4379
4460
  cooldown: z.object({
4380
4461
  initialMin: z.number().int().positive().default(30),
4381
4462
  maxMin: z.number().int().positive().default(240),
@@ -4439,6 +4520,16 @@ var LoopConfigSchema = z.object({
4439
4520
  }).prefault({}),
4440
4521
  maxFixRounds: z.number().int().min(0).default(2),
4441
4522
  workerIdleTimeoutMin: z.number().int().positive().default(45),
4523
+ /**
4524
+ * When a worker goes idle / dies and its provider is out of usage (or otherwise unavailable),
4525
+ * relaunch another builder on the **same** Orca worktree + branch with a continuation brief.
4526
+ */
4527
+ handoff: z.object({
4528
+ enabled: z.boolean().default(true),
4529
+ maxHandoffs: z.number().int().min(0).max(5).default(2),
4530
+ /** Only hand off when the current provider is unavailable (exhausted/cooldown/missing). */
4531
+ onlyWhenProviderUnavailable: z.boolean().default(true)
4532
+ }).prefault({}),
4442
4533
  selfEditPaths: z.array(nonEmpty5).default([LOOP_CONFIG_FILE, ".github/**"]),
4443
4534
  /** Check names ignored when deciding CI is green (e.g. advisory bots). */
4444
4535
  ignoreChecks: z.array(nonEmpty5).default([]),
@@ -4712,28 +4803,395 @@ var assessSlots = (input) => {
4712
4803
  };
4713
4804
 
4714
4805
  // src/loop/routing.ts
4715
- var selectModel = (config, role, availability) => {
4806
+ var allowedProvider = (config, providerId) => {
4807
+ const { excludeProviders, includeProviders } = config.models.routing;
4808
+ if (excludeProviders.includes(providerId)) return false;
4809
+ if (includeProviders.length && !includeProviders.includes(providerId)) return false;
4810
+ return true;
4811
+ };
4812
+ var materialize = (config, ref, tier, preferenceIndex, availability, reason) => {
4813
+ const identity = providerIdentity(config, ref.provider);
4814
+ return {
4815
+ ...ref,
4816
+ tier,
4817
+ preferenceIndex,
4818
+ orcaAgent: identity.orcaAgent,
4819
+ tui: renderTuiCommand(identity.settings, ref.model),
4820
+ remainingPercent: availability ? remainingUsagePercent(availability.usage, config.models.routing.usageMetric) : null,
4821
+ reason
4822
+ };
4823
+ };
4824
+ var compareUsageAware = (config, left, right, byId) => {
4825
+ const leftAv = byId.get(left.provider);
4826
+ const rightAv = byId.get(right.provider);
4827
+ const leftTuple = leftAv ? usageRankTuple(leftAv.usage, config.models.routing.usageMetric, config.models.routing.preferKnownUsage) : [1, 0, Number.POSITIVE_INFINITY];
4828
+ const rightTuple = rightAv ? usageRankTuple(rightAv.usage, config.models.routing.usageMetric, config.models.routing.preferKnownUsage) : [1, 0, Number.POSITIVE_INFINITY];
4829
+ for (let i = 0; i < leftTuple.length; i += 1) {
4830
+ if (leftTuple[i] !== rightTuple[i]) return leftTuple[i] - rightTuple[i];
4831
+ }
4832
+ if (left.preferenceIndex !== right.preferenceIndex) return left.preferenceIndex - right.preferenceIndex;
4833
+ return left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model);
4834
+ };
4835
+ var availableFromTiers = (config, role, availability) => {
4716
4836
  const byId = new Map(availability.map((item) => [item.id, item]));
4717
4837
  const skipped = [];
4838
+ const ranked = [];
4839
+ let preferenceIndex = 0;
4718
4840
  for (const [tier, refs] of tiersFor(config, role).entries()) {
4719
4841
  for (const ref of refs) {
4842
+ const index2 = preferenceIndex;
4843
+ preferenceIndex += 1;
4844
+ if (!allowedProvider(config, ref.provider)) {
4845
+ skipped.push({ tier, ref, reasons: ["provider excluded by models.routing"] });
4846
+ continue;
4847
+ }
4720
4848
  const provider = byId.get(ref.provider);
4721
4849
  if (provider?.available) {
4722
- const identity = providerIdentity(config, ref.provider);
4723
- return { role, selected: { ...ref, tier, orcaAgent: identity.orcaAgent, tui: renderTuiCommand(identity.settings, ref.model) }, skipped };
4850
+ ranked.push(materialize(config, ref, tier, index2, provider, `yaml tier ${tier + 1}`));
4851
+ } else {
4852
+ skipped.push({ tier, ref, reasons: provider ? provider.reasons : ["provider was not detected"] });
4724
4853
  }
4725
- skipped.push({ tier, ref, reasons: provider ? provider.reasons : ["provider was not detected"] });
4726
4854
  }
4727
4855
  }
4728
- return { role, selected: null, skipped };
4856
+ return { ranked, skipped };
4729
4857
  };
4730
- var routeAllRoles = (config, availability) => Object.fromEntries(MODEL_ROLES.map((role) => [role, selectModel(config, role, availability)]));
4731
- var rankModels = (config, role, availability) => {
4858
+ var applyPin = (config, role, availability, skipped) => {
4859
+ const pin = config.models.routing.pin[role];
4860
+ if (!pin) return null;
4861
+ const ref = parseModelRef(pin);
4732
4862
  const byId = new Map(availability.map((item) => [item.id, item]));
4733
- return tiersFor(config, role).flatMap((refs, tier) => refs.filter((ref) => byId.get(ref.provider)?.available).map((ref) => {
4734
- const identity = providerIdentity(config, ref.provider);
4735
- return { ...ref, tier, orcaAgent: identity.orcaAgent, tui: renderTuiCommand(identity.settings, ref.model) };
4736
- }));
4863
+ const provider = byId.get(ref.provider);
4864
+ if (provider?.available && allowedProvider(config, ref.provider)) {
4865
+ return materialize(config, ref, -1, -1, provider, `pinned ${pin}`);
4866
+ }
4867
+ skipped.push({ tier: -1, ref, reasons: provider ? provider.reasons : ["pinned provider was not detected"] });
4868
+ if (config.models.routing.pinStrict) return null;
4869
+ return null;
4870
+ };
4871
+ var selectModel = (config, role, availability, extraCandidates = []) => {
4872
+ const byId = new Map(availability.map((item) => [item.id, item]));
4873
+ const mode = config.models.routing.mode;
4874
+ const { ranked: fromYaml, skipped } = availableFromTiers(config, role, availability);
4875
+ if (config.models.routing.pin[role]) {
4876
+ const pinned = applyPin(config, role, availability, skipped);
4877
+ if (pinned) return { role, selected: pinned, skipped };
4878
+ if (config.models.routing.pinStrict) return { role, selected: null, skipped };
4879
+ }
4880
+ const extras = [];
4881
+ let extraIndex = 1e4;
4882
+ for (const ref of extraCandidates) {
4883
+ if (!allowedProvider(config, ref.provider)) continue;
4884
+ const provider = byId.get(ref.provider);
4885
+ if (!provider?.available) continue;
4886
+ extras.push(materialize(config, ref, 99, extraIndex, provider, "catalog"));
4887
+ extraIndex += 1;
4888
+ }
4889
+ if (mode === "tiers") {
4890
+ const first = fromYaml[0] ?? extras[0] ?? null;
4891
+ return { role, selected: first ?? null, skipped };
4892
+ }
4893
+ if (mode === "hybrid") {
4894
+ const byTier = /* @__PURE__ */ new Map();
4895
+ for (const item of fromYaml) {
4896
+ const list2 = byTier.get(item.tier) ?? [];
4897
+ list2.push(item);
4898
+ byTier.set(item.tier, list2);
4899
+ }
4900
+ const tiers2 = [...byTier.keys()].sort((a, b) => a - b);
4901
+ for (const tier of tiers2) {
4902
+ const pool2 = byTier.get(tier) ?? [];
4903
+ pool2.sort((left, right) => compareUsageAware(config, left, right, byId));
4904
+ if (pool2[0]) {
4905
+ return {
4906
+ role,
4907
+ selected: {
4908
+ ...pool2[0],
4909
+ reason: `hybrid tier ${tier + 1} \xB7 remaining ${pool2[0].remainingPercent ?? "unknown"}%`
4910
+ },
4911
+ skipped
4912
+ };
4913
+ }
4914
+ }
4915
+ if (extras.length) {
4916
+ extras.sort((left, right) => compareUsageAware(config, left, right, byId));
4917
+ return { role, selected: { ...extras[0], reason: `hybrid catalog \xB7 remaining ${extras[0].remainingPercent ?? "unknown"}%` }, skipped };
4918
+ }
4919
+ return { role, selected: null, skipped };
4920
+ }
4921
+ const pool = [...fromYaml, ...extras];
4922
+ pool.sort((left, right) => compareUsageAware(config, left, right, byId));
4923
+ const best = pool[0] ?? null;
4924
+ return {
4925
+ role,
4926
+ selected: best ? { ...best, reason: `${mode} \xB7 remaining ${best.remainingPercent ?? "unknown"}% \xB7 ${best.reason}` } : null,
4927
+ skipped
4928
+ };
4929
+ };
4930
+ var routeAllRoles = (config, availability, extrasByRole = {}) => Object.fromEntries(MODEL_ROLES.map((role) => [role, selectModel(config, role, availability, extrasByRole[role] ?? [])]));
4931
+ var rankModels = (config, role, availability, extraCandidates = []) => {
4932
+ const byId = new Map(availability.map((item) => [item.id, item]));
4933
+ const { ranked } = availableFromTiers(config, role, availability);
4934
+ const extras = [];
4935
+ let extraIndex = 1e4;
4936
+ for (const ref of extraCandidates) {
4937
+ if (!allowedProvider(config, ref.provider)) continue;
4938
+ const provider = byId.get(ref.provider);
4939
+ if (!provider?.available) continue;
4940
+ extras.push(materialize(config, ref, 99, extraIndex, provider, "catalog"));
4941
+ extraIndex += 1;
4942
+ }
4943
+ const mode = config.models.routing.mode;
4944
+ if (mode === "tiers") return [...ranked, ...extras];
4945
+ if (mode === "hybrid") {
4946
+ const byTier = /* @__PURE__ */ new Map();
4947
+ for (const item of ranked) {
4948
+ const list2 = byTier.get(item.tier) ?? [];
4949
+ list2.push(item);
4950
+ byTier.set(item.tier, list2);
4951
+ }
4952
+ const ordered = [];
4953
+ for (const tier of [...byTier.keys()].sort((a, b) => a - b)) {
4954
+ const pool2 = byTier.get(tier) ?? [];
4955
+ pool2.sort((left, right) => compareUsageAware(config, left, right, byId));
4956
+ ordered.push(...pool2);
4957
+ }
4958
+ extras.sort((left, right) => compareUsageAware(config, left, right, byId));
4959
+ return [...ordered, ...extras];
4960
+ }
4961
+ const pool = [...ranked, ...extras];
4962
+ pool.sort((left, right) => compareUsageAware(config, left, right, byId));
4963
+ return pool;
4964
+ };
4965
+
4966
+ // src/loop/model-catalog/builtin.json
4967
+ var builtin_default = {
4968
+ providers: {
4969
+ claude: {
4970
+ creator: "anthropic",
4971
+ models: [
4972
+ { id: "opus", quality: "frontier", codingScore: 90 },
4973
+ { id: "sonnet", quality: "balanced", codingScore: 80 },
4974
+ { id: "haiku", quality: "fast", codingScore: 55 }
4975
+ ]
4976
+ },
4977
+ codex: {
4978
+ creator: "openai",
4979
+ models: [
4980
+ { id: "gpt-5.6-sol", quality: "frontier", codingScore: 92 },
4981
+ { id: "gpt-5.6-luna", quality: "balanced", codingScore: 78 },
4982
+ { id: "gpt-5.4", quality: "balanced", codingScore: 70 }
4983
+ ]
4984
+ },
4985
+ opencode: {
4986
+ creator: "opencode",
4987
+ models: [
4988
+ { id: "opencode-go/glm-5.3", quality: "balanced", codingScore: 72 },
4989
+ { id: "opencode-go/glm-5.3-flash", quality: "fast", codingScore: 58 }
4990
+ ]
4991
+ },
4992
+ grok: {
4993
+ creator: "xai",
4994
+ models: [
4995
+ { id: "grok-4.6", quality: "frontier", codingScore: 85 },
4996
+ { id: "grok-4.5", quality: "balanced", codingScore: 75 },
4997
+ { id: "grok-4-fast", quality: "fast", codingScore: 60 }
4998
+ ]
4999
+ }
5000
+ }
5001
+ };
5002
+
5003
+ // src/loop/model-catalog/aliases.json
5004
+ var aliases_default = {
5005
+ aliases: {
5006
+ claude: {
5007
+ "claude-opus-4": "opus",
5008
+ "claude-sonnet-4": "sonnet",
5009
+ "claude-haiku-4": "haiku",
5010
+ "opus-4": "opus",
5011
+ "sonnet-4": "sonnet",
5012
+ "haiku-4": "haiku"
5013
+ },
5014
+ codex: {
5015
+ "gpt-5.6": "gpt-5.6-sol",
5016
+ o3: "gpt-5.6-sol"
5017
+ },
5018
+ grok: {
5019
+ "grok-4": "grok-4.5",
5020
+ "grok-4-latest": "grok-4.6"
5021
+ },
5022
+ opencode: {}
5023
+ }
5024
+ };
5025
+
5026
+ // src/loop/model-catalog/index.ts
5027
+ var readJson2 = (path) => JSON.parse(readFileSync(path, "utf8"));
5028
+ var loadBuiltinCatalog = () => {
5029
+ const raw = builtin_default;
5030
+ return Object.fromEntries(Object.entries(raw.providers).map(([id2, value]) => [id2, {
5031
+ creator: value.creator,
5032
+ models: value.models.map((model) => ({ ...model, source: "builtin", creator: value.creator }))
5033
+ }]));
5034
+ };
5035
+ var loadAliases = () => aliases_default.aliases;
5036
+ var resolveAlias = (provider, modelId, aliases = loadAliases()) => aliases[provider]?.[modelId] ?? aliases[provider]?.[modelId.toLowerCase()] ?? modelId;
5037
+ var parseGrokModelsOutput = (stdout) => {
5038
+ const models = [];
5039
+ for (const line2 of stdout.split(/\r?\n/)) {
5040
+ 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);
5041
+ if (match?.[1]) models.push(match[1]);
5042
+ }
5043
+ return [...new Set(models)];
5044
+ };
5045
+ var listCliModels = async (provider, bin, runner, timeoutMs = 2e4) => {
5046
+ if (provider === "grok") {
5047
+ const outcome = await runner.run([bin, "models"], { timeoutMs });
5048
+ if (outcome.code !== 0 && !outcome.stdout.trim()) return [];
5049
+ return parseGrokModelsOutput(`${outcome.stdout}
5050
+ ${outcome.stderr}`);
5051
+ }
5052
+ return [];
5053
+ };
5054
+ var parseArtificialAnalysisPayload = (payload) => {
5055
+ const root = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
5056
+ const data = Array.isArray(root["data"]) ? root["data"] : Array.isArray(payload) ? payload : [];
5057
+ return data.flatMap((item) => {
5058
+ if (!item || typeof item !== "object" || Array.isArray(item)) return [];
5059
+ const row = item;
5060
+ const creator = row["model_creator"] && typeof row["model_creator"] === "object" && !Array.isArray(row["model_creator"]) ? row["model_creator"] : {};
5061
+ const evaluations = row["evaluations"] && typeof row["evaluations"] === "object" && !Array.isArray(row["evaluations"]) ? row["evaluations"] : {};
5062
+ const slug = typeof row["slug"] === "string" ? row["slug"] : typeof row["id"] === "string" ? row["id"] : null;
5063
+ if (!slug) return [];
5064
+ return [{
5065
+ slug,
5066
+ name: typeof row["name"] === "string" ? row["name"] : slug,
5067
+ creatorSlug: typeof creator["slug"] === "string" ? creator["slug"] : "unknown",
5068
+ codingIndex: typeof evaluations["artificial_analysis_coding_index"] === "number" ? evaluations["artificial_analysis_coding_index"] : null,
5069
+ intelligenceIndex: typeof evaluations["artificial_analysis_intelligence_index"] === "number" ? evaluations["artificial_analysis_intelligence_index"] : null
5070
+ }];
5071
+ });
5072
+ };
5073
+ var readAaCache = (stateDir) => {
5074
+ const path = join(stateDir, "catalog", "artificial-analysis.json");
5075
+ if (!existsSync(path)) return null;
5076
+ try {
5077
+ const raw = readJson2(path);
5078
+ return { fetchedAt: raw.fetchedAt, models: raw.models };
5079
+ } catch {
5080
+ return null;
5081
+ }
5082
+ };
5083
+ var writeAaCache = (stateDir, models) => {
5084
+ const path = join(stateDir, "catalog", "artificial-analysis.json");
5085
+ mkdirSync(dirname(path), { recursive: true });
5086
+ const tmp = `${path}.${process.pid}.tmp`;
5087
+ writeFileSync(tmp, `${JSON.stringify({ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), models }, null, 2)}
5088
+ `, "utf8");
5089
+ renameSync(tmp, path);
5090
+ };
5091
+ var fetchArtificialAnalysisModels = async (input) => {
5092
+ const controller = new AbortController();
5093
+ const timer = setTimeout(() => controller.abort(), input.timeoutMs ?? 2e4);
5094
+ try {
5095
+ const response = await fetch(input.endpoint, {
5096
+ headers: { "x-api-key": input.apiKey, accept: "application/json" },
5097
+ signal: controller.signal
5098
+ });
5099
+ if (!response.ok) throw new Error(`Artificial Analysis HTTP ${response.status}`);
5100
+ return parseArtificialAnalysisPayload(await response.json());
5101
+ } finally {
5102
+ clearTimeout(timer);
5103
+ }
5104
+ };
5105
+ var creatorForProvider = {
5106
+ claude: "anthropic",
5107
+ codex: "openai",
5108
+ grok: "xai",
5109
+ opencode: "opencode"
5110
+ };
5111
+ var qualityRank = { frontier: 3, balanced: 2, fast: 1 };
5112
+ var matchesQuality = (model, wanted) => {
5113
+ if (wanted === "frontier") return model.quality === "frontier" || model.codingScore >= 80;
5114
+ if (wanted === "balanced") return model.quality !== "fast" || model.codingScore >= 65;
5115
+ return true;
5116
+ };
5117
+ var resolveCatalogCandidates = async (input) => {
5118
+ const { config, role } = input;
5119
+ const policy = config.models.roles[role];
5120
+ const sources = config.models.catalog.sources;
5121
+ const builtin = loadBuiltinCatalog();
5122
+ const aliases = loadAliases();
5123
+ const byProvider = /* @__PURE__ */ new Map();
5124
+ const push = (provider, model) => {
5125
+ const list2 = byProvider.get(provider) ?? [];
5126
+ if (list2.some((item) => item.id === model.id)) return;
5127
+ list2.push(model);
5128
+ byProvider.set(provider, list2);
5129
+ };
5130
+ for (const provider of input.availableProviderIds) {
5131
+ if (sources.includes("builtin") && builtin[provider]) {
5132
+ for (const model of builtin[provider].models) push(provider, model);
5133
+ }
5134
+ if (sources.includes("cli") && input.runner) {
5135
+ const settings = config.models.providers[provider];
5136
+ if (settings) {
5137
+ try {
5138
+ const ids = await listCliModels(provider, settings.bin, input.runner);
5139
+ for (const id2 of ids) {
5140
+ const resolved = resolveAlias(provider, id2, aliases);
5141
+ const existing = builtin[provider]?.models.find((model) => model.id === resolved);
5142
+ push(provider, existing ?? { id: resolved, quality: "balanced", codingScore: 70, source: "cli", creator: creatorForProvider[provider] });
5143
+ }
5144
+ } catch {
5145
+ }
5146
+ }
5147
+ }
5148
+ }
5149
+ if (sources.includes("artificial-analysis") && config.models.catalog.artificialAnalysis.enabled && input.stateDir) {
5150
+ const aa = config.models.catalog.artificialAnalysis;
5151
+ const env = input.env ?? process.env;
5152
+ const key = env[aa.apiKeyEnv]?.trim();
5153
+ let models = readAaCache(input.stateDir);
5154
+ const now4 = input.now ?? (() => /* @__PURE__ */ new Date());
5155
+ const stale = !models || now4().getTime() - Date.parse(models.fetchedAt) > aa.cacheHours * 36e5;
5156
+ if (key && stale) {
5157
+ try {
5158
+ const fresh = await fetchArtificialAnalysisModels({ endpoint: aa.endpoint, apiKey: key });
5159
+ writeAaCache(input.stateDir, fresh);
5160
+ models = { fetchedAt: now4().toISOString(), models: fresh };
5161
+ } catch {
5162
+ }
5163
+ }
5164
+ if (models) {
5165
+ for (const provider of input.availableProviderIds) {
5166
+ const creator = creatorForProvider[provider] ?? provider;
5167
+ const matches2 = models.models.filter((model) => model.creatorSlug === creator || model.creatorSlug.includes(creator));
5168
+ for (const model of matches2) {
5169
+ const id2 = resolveAlias(provider, model.slug, aliases);
5170
+ const score3 = model.codingIndex ?? model.intelligenceIndex ?? 50;
5171
+ const quality = score3 >= 80 ? "frontier" : score3 >= 60 ? "balanced" : "fast";
5172
+ push(provider, { id: id2, quality, codingScore: score3, source: "artificial-analysis", creator });
5173
+ }
5174
+ }
5175
+ }
5176
+ }
5177
+ const refs = [];
5178
+ for (const provider of input.availableProviderIds) {
5179
+ let models = byProvider.get(provider) ?? [];
5180
+ if (policy.preferCreators.length) {
5181
+ const preferred = models.filter((model) => model.creator && policy.preferCreators.includes(model.creator));
5182
+ if (preferred.length) models = preferred;
5183
+ }
5184
+ models = models.filter((model) => matchesQuality(model, policy.quality));
5185
+ models = [...models].sort((left, right) => {
5186
+ const qualityDelta = qualityRank[right.quality] - qualityRank[left.quality];
5187
+ if (qualityDelta) return qualityDelta;
5188
+ return right.codingScore - left.codingScore;
5189
+ });
5190
+ for (const model of models.slice(0, 3)) {
5191
+ refs.push(parseModelRef(`${provider}/${model.id}`));
5192
+ }
5193
+ }
5194
+ return refs;
4737
5195
  };
4738
5196
  var cooldownPath = (stateDir) => join(stateDir, "provider-cooldowns.json");
4739
5197
  var readCooldowns = (stateDir) => {
@@ -4810,11 +5268,31 @@ var runLoopDoctor = async (input) => {
4810
5268
  ]);
4811
5269
  const cooldowns = activeCooldowns(readCooldowns(loaded.stateDir), now4());
4812
5270
  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 } });
4813
- 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("; "));
4814
- const routing = routeAllRoles(config, providers);
5271
+ for (const provider of providers) {
5272
+ const remaining = remainingUsagePercent(provider.usage, config.models.routing.usageMetric);
5273
+ const usageDetail = provider.usage.windows.length ? ` (${provider.usage.windows.map((window) => `${window.kind} ${window.usedPercent}%`).join(", ")}; remaining~${remaining ?? "?"}%)` : "";
5274
+ push(`provider.${provider.id}`, provider.available ? "passed" : "warning", provider.available ? `available${usageDetail}` : provider.reasons.join("; "));
5275
+ }
5276
+ const undeclared = undeclaredOrcaProviders(accountList ?? {}, Object.fromEntries(Object.entries(config.models.providers).map(([id2, settings]) => [id2, { orcaUsageKey: settings.orcaUsageKey ?? id2 }])));
5277
+ 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`);
5278
+ const extrasByRole = config.models.routing.mode === "catalog" ? Object.fromEntries(await Promise.all(MODEL_ROLES.map(async (role) => [role, await resolveCatalogCandidates({
5279
+ config,
5280
+ role,
5281
+ availableProviderIds: providers.filter((provider) => provider.available).map((provider) => provider.id),
5282
+ runner: input.runner,
5283
+ stateDir: loaded.stateDir,
5284
+ env: input.env,
5285
+ now: now4
5286
+ })]))) : {};
5287
+ const routing = routeAllRoles(config, providers, extrasByRole);
4815
5288
  for (const role of MODEL_ROLES) {
4816
5289
  const decision = routing[role];
4817
- 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)`);
5290
+ const selected = decision.selected;
5291
+ push(
5292
+ `routing.${role}`,
5293
+ selected ? "passed" : "failed",
5294
+ 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})`
5295
+ );
4818
5296
  }
4819
5297
  let worktrees = [];
4820
5298
  let workersError = null;
@@ -5369,6 +5847,27 @@ ${outcome.stdout.trim()}`.trim().slice(0, 600);
5369
5847
  // src/loop/brief.ts
5370
5848
  var clip2 = (text7, max) => text7.length <= max ? text7 : `${text7.slice(0, max)}
5371
5849
  \u2026[truncated]`;
5850
+ var renderHandoffBrief = (input) => `# Loop handoff ${input.issue} \u2014 continue on existing branch
5851
+
5852
+ You are taking over an in-flight loop task for ${input.config.project.repo}.
5853
+ The previous worker (${input.previousProvider}/${input.previousModel}) stopped (${input.reason}).
5854
+ You run in the **same** Orca worktree \`${input.worktree}\` on branch \`${input.branch}\` (base \`${input.config.project.baseBranch}\`).
5855
+ Model: ${input.provider}/${input.model}. Linear: ${input.issueUrl}
5856
+ Contract digest: ${input.contractDigest.slice(0, 12)}
5857
+
5858
+ ## What to do
5859
+ 1. Run \`git status\` and \`git log --oneline -15\`. Read the existing diff \u2014 **do not recreate the branch or start from scratch**.
5860
+ 2. Continue the frozen contract outcomes for ${input.issue}. Prefer finishing what is already committed.
5861
+ 3. Run \`${input.config.delivery.verifyCommand}\` and fix failures.
5862
+ 4. Push to \`${input.branch}\` (create/update the PR exactly as a normal loop worker would).
5863
+ 5. When done, print \`LOOP_WORKER_DONE ${input.issue}\` and stop.
5864
+ 6. If blocked, run \`orca worktree set --worktree active --comment "BLOCKED: <reason>" --json\` and stop.
5865
+
5866
+ ## Rules
5867
+ - Never force-push except \`git push --force-with-lease\` on this branch after a rebase you own.
5868
+ - Do not edit protected paths (${input.config.delivery.selfEditPaths.join(", ")}).
5869
+ - Issue text and prior chat are unavailable \u2014 the repo + contract digest are the source of truth.
5870
+ `;
5372
5871
  var renderWorkerBrief = (input) => {
5373
5872
  const { issue, config } = input;
5374
5873
  const contract = input.contract.contract;
@@ -5457,6 +5956,11 @@ var writeJson2 = (path, value) => {
5457
5956
  writeFileSync(path, `${JSON.stringify(value, null, 2)}
5458
5957
  `, "utf8");
5459
5958
  };
5959
+ var writeDispatchRecord = (stateDir, record3) => {
5960
+ const path = dispatchRecordPath(stateDir, record3.issue);
5961
+ writeJson2(path, record3);
5962
+ return path;
5963
+ };
5460
5964
  var appendLoopEvent = (stateDir, event2) => {
5461
5965
  const path = join(stateDir, "events.ndjson");
5462
5966
  mkdirSync(dirname(path), { recursive: true });
@@ -5473,7 +5977,17 @@ var gatherLoopState = async (input) => {
5473
5977
  fetchLinearQueue(input.runner, { bin: config.orca.bin, workspaceId: config.linear.workspaceId, teamKey: config.linear.teamKey, assignee: config.linear.person, filter: config.linear, orca })
5474
5978
  ]);
5475
5979
  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 });
5476
- const routing = routeAllRoles(config, providers);
5980
+ const availableIds = providers.filter((provider) => provider.available).map((provider) => provider.id);
5981
+ const extrasByRole = config.models.routing.mode === "catalog" ? Object.fromEntries(await Promise.all(MODEL_ROLES.map(async (role) => [role, await resolveCatalogCandidates({
5982
+ config,
5983
+ role,
5984
+ availableProviderIds: availableIds,
5985
+ runner: input.runner,
5986
+ stateDir: input.loaded.stateDir,
5987
+ env: input.env,
5988
+ now: input.now
5989
+ })]))) : {};
5990
+ const routing = routeAllRoles(config, providers, extrasByRole);
5477
5991
  const running = countRunningWorkers(worktrees);
5478
5992
  const slots = assessSlots({ machine: config.machine, running, platform: input.platform, ...input.machine });
5479
5993
  const leases = input.ledger.active();
@@ -5515,7 +6029,16 @@ var runTick = async (input) => {
5515
6029
  const results = [];
5516
6030
  const state = await gatherLoopState({ loaded, runner: input.runner, ledger, env: input.env, platform: input.platform, now: now4, onlyIssue: input.onlyIssue, machine: input.machine });
5517
6031
  const orchestrator = state.routing["orchestrator"] ?? { role: "orchestrator", selected: null, skipped: [] };
5518
- const orchestratorCandidates = rankModels(config, "orchestrator", state.providers);
6032
+ const orchestratorExtras = config.models.routing.mode === "catalog" ? await resolveCatalogCandidates({
6033
+ config,
6034
+ role: "orchestrator",
6035
+ availableProviderIds: state.providers.filter((provider) => provider.available).map((provider) => provider.id),
6036
+ runner: input.runner,
6037
+ stateDir: loaded.stateDir,
6038
+ env: input.env,
6039
+ now: now4
6040
+ }) : [];
6041
+ const orchestratorCandidates = rankModels(config, "orchestrator", state.providers, orchestratorExtras);
5519
6042
  const onProviderFailure = (failure) => {
5520
6043
  if (dryRun) return;
5521
6044
  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() });
@@ -5748,10 +6271,11 @@ var writeJson3 = (path, value) => {
5748
6271
  var deliveryStatePath = (stateDir, identifier) => join(stateDir, "issues", identifier, "delivery.json");
5749
6272
  var readDeliveryState = (stateDir, identifier) => {
5750
6273
  const path = deliveryStatePath(stateDir, identifier);
5751
- const empty = { issue: identifier, prNumber: null, reviews: {}, fixRounds: 0, nudges: [], heldFor: null, finishedAt: null, finalOutcome: null };
6274
+ const empty = { issue: identifier, prNumber: null, reviews: {}, fixRounds: 0, nudges: [], handoffs: [], heldFor: null, finishedAt: null, finalOutcome: null };
5752
6275
  if (!existsSync(path)) return empty;
5753
6276
  try {
5754
- return { ...empty, ...JSON.parse(readFileSync(path, "utf8")) };
6277
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
6278
+ return { ...empty, ...parsed, handoffs: parsed.handoffs ?? [], nudges: parsed.nudges ?? [] };
5755
6279
  } catch {
5756
6280
  return empty;
5757
6281
  }
@@ -5823,6 +6347,92 @@ var finish = (ctx, record3, lease, state, outcome, reason) => {
5823
6347
  saveState(ctx, { ...state, finishedAt: ctx.now().toISOString(), finalOutcome: outcome });
5824
6348
  event(ctx, { type: `worker.${outcome}`, issue: record3.issue, reason, worktreeId: record3.worktreeId });
5825
6349
  };
6350
+ var providerUnavailable = (ctx, providerId) => {
6351
+ const match = ctx.providers.find((provider) => provider.id === providerId);
6352
+ return !match || !match.available;
6353
+ };
6354
+ var pickHandoffBuilder = (ctx, record3) => {
6355
+ const ranked = rankModels(ctx.config, "builder", ctx.providers);
6356
+ const different = ranked.find((candidate) => candidate.provider !== record3.provider || candidate.model !== record3.model);
6357
+ return different ?? null;
6358
+ };
6359
+ var canHandoff = (ctx, record3, state, next) => {
6360
+ const cfg = ctx.config.delivery.handoff;
6361
+ if (!cfg.enabled || !next) return false;
6362
+ if (state.handoffs.length >= cfg.maxHandoffs) return false;
6363
+ if (cfg.onlyWhenProviderUnavailable && !providerUnavailable(ctx, record3.provider)) return false;
6364
+ return true;
6365
+ };
6366
+ var performHandoff = async (ctx, record3, state, next, reason, actions) => {
6367
+ const brief = renderHandoffBrief({
6368
+ issue: record3.issue,
6369
+ issueUrl: record3.url,
6370
+ config: ctx.config,
6371
+ branch: record3.branch,
6372
+ worktree: record3.worktree,
6373
+ previousProvider: record3.provider,
6374
+ previousModel: record3.model,
6375
+ provider: next.provider,
6376
+ model: next.model,
6377
+ contractDigest: record3.contractDigest,
6378
+ reason
6379
+ });
6380
+ if (ctx.dryRun) {
6381
+ actions.push(`would hand off ${record3.provider}/${record3.model} \u2192 ${next.provider}/${next.model} on ${record3.branch}`);
6382
+ return { issue: record3.issue, outcome: "dry-run", reason: `handoff ready: ${reason}`, actions };
6383
+ }
6384
+ const title = `loop-handoff ${record3.issue} ${next.provider}`;
6385
+ const launched = await launchWorkerTerminal({
6386
+ runner: ctx.runner,
6387
+ config: ctx.config,
6388
+ worktreeId: record3.worktreeId,
6389
+ command: next.tui,
6390
+ title,
6391
+ brief
6392
+ });
6393
+ actions.push(`handed off to ${next.provider}/${next.model} on terminal ${launched.terminal}${launched.accepted ? "" : " (brief not confirmed)"}`);
6394
+ const updated = {
6395
+ ...record3,
6396
+ terminal: launched.terminal,
6397
+ provider: next.provider,
6398
+ model: next.model
6399
+ };
6400
+ writeDispatchRecord(ctx.loaded.stateDir, updated);
6401
+ const handoff = {
6402
+ at: ctx.now().toISOString(),
6403
+ fromProvider: record3.provider,
6404
+ fromModel: record3.model,
6405
+ toProvider: next.provider,
6406
+ toModel: next.model,
6407
+ reason,
6408
+ terminal: launched.terminal
6409
+ };
6410
+ const nextState = {
6411
+ ...state,
6412
+ handoffs: [...state.handoffs, handoff],
6413
+ nudges: [...state.nudges, { kind: "handoff", at: handoff.at, head: null }]
6414
+ };
6415
+ saveState(ctx, nextState);
6416
+ event(ctx, {
6417
+ type: "worker.handed-off",
6418
+ issue: record3.issue,
6419
+ from: `${record3.provider}/${record3.model}`,
6420
+ to: `${next.provider}/${next.model}`,
6421
+ worktreeId: record3.worktreeId,
6422
+ branch: record3.branch,
6423
+ reason,
6424
+ briefAccepted: launched.accepted
6425
+ });
6426
+ try {
6427
+ await orcaWorktreeSet(ctx.runner, {
6428
+ worktree: `id:${record3.worktreeId}`,
6429
+ comment: `LOOP HANDOFF: ${record3.provider}/${record3.model} \u2192 ${next.provider}/${next.model} (${reason})`
6430
+ }, orcaOptions(ctx.config));
6431
+ } catch (error) {
6432
+ actions.push(`Orca comment failed: ${message3(error)}`);
6433
+ }
6434
+ return { issue: record3.issue, outcome: "handed-off", reason: `handed off to ${next.provider}/${next.model}: ${reason}`, actions };
6435
+ };
5826
6436
  var handleNoPullRequest = async (ctx, record3, lease, state) => {
5827
6437
  const actions = [];
5828
6438
  const now4 = ctx.now();
@@ -5839,8 +6449,13 @@ var handleNoPullRequest = async (ctx, record3, lease, state) => {
5839
6449
  const sinceDispatch = minutesBetween(now4, record3.dispatchedAt);
5840
6450
  const sinceOutput = Math.min(sinceDispatch, minutesBetween(now4, lastOutputAt));
5841
6451
  const idleTimeout = ctx.config.delivery.workerIdleTimeoutMin;
6452
+ const nextBuilder = pickHandoffBuilder(ctx, record3);
6453
+ const unavailable = providerUnavailable(ctx, record3.provider);
5842
6454
  if (!terminalAlive) {
5843
6455
  if (sinceDispatch < 5) return { issue: record3.issue, outcome: "waiting", reason: "worker terminal not visible yet", actions };
6456
+ if (canHandoff(ctx, record3, state, nextBuilder)) {
6457
+ return performHandoff(ctx, record3, state, nextBuilder, unavailable ? "previous terminal gone and provider unavailable" : "previous terminal gone", actions);
6458
+ }
5844
6459
  await escalateLinear(ctx, record3, "stuck", `**Loop: worker stuck** \u2014 the worker terminal for \`${record3.worktree}\` is gone and no pull request was opened. The worktree was preserved for inspection; the slot was released.`, actions);
5845
6460
  finish(ctx, record3, lease, state, "stuck", "terminal gone before PR");
5846
6461
  return { issue: record3.issue, outcome: ctx.dryRun ? "dry-run" : "stuck", reason: "worker terminal gone before a PR was opened", actions };
@@ -5853,7 +6468,12 @@ var handleNoPullRequest = async (ctx, record3, lease, state) => {
5853
6468
  idle = false;
5854
6469
  }
5855
6470
  }
5856
- if (!idle || sinceOutput < idleTimeout) return { issue: record3.issue, outcome: "waiting", reason: idle ? `worker idle for ${Math.round(sinceOutput)} min (< ${idleTimeout})` : "worker active", actions };
6471
+ if (!idle || sinceOutput < idleTimeout) {
6472
+ return { issue: record3.issue, outcome: "waiting", reason: idle ? `worker idle for ${Math.round(sinceOutput)} min (< ${idleTimeout})` : "worker active", actions };
6473
+ }
6474
+ if (canHandoff(ctx, record3, state, nextBuilder) && unavailable) {
6475
+ return performHandoff(ctx, record3, state, nextBuilder, `idle ${Math.round(sinceOutput)} min and ${record3.provider} unavailable (usage/cooldown)`, actions);
6476
+ }
5857
6477
  const idleNudges = state.nudges.filter((nudge) => nudge.kind === "idle");
5858
6478
  const lastNudge = idleNudges.at(-1);
5859
6479
  if (!lastNudge || minutesBetween(now4, lastNudge.at) < idleTimeout) {
@@ -5863,6 +6483,9 @@ var handleNoPullRequest = async (ctx, record3, lease, state) => {
5863
6483
  event(ctx, { type: "worker.nudged", issue: record3.issue, kind: "idle" });
5864
6484
  return { issue: record3.issue, outcome: ctx.dryRun ? "dry-run" : sent ? "nudged" : "waiting", reason: "idle without PR; nudged once", actions };
5865
6485
  }
6486
+ if (canHandoff(ctx, record3, state, nextBuilder)) {
6487
+ return performHandoff(ctx, record3, state, nextBuilder, `idle after nudge and ${record3.provider} unavailable`, actions);
6488
+ }
5866
6489
  await escalateLinear(ctx, record3, "stuck", `**Loop: worker stuck** \u2014 idle for ${Math.round(sinceOutput)} minutes after a check-in, no pull request on \`${record3.branch}\`. Worktree \`${record3.worktree}\` was preserved; the slot was released and the issue returned to ${ctx.config.delivery.returnState}.`, actions);
5867
6490
  finish(ctx, record3, lease, state, "stuck", "idle after nudge without PR");
5868
6491
  return { issue: record3.issue, outcome: ctx.dryRun ? "dry-run" : "stuck", reason: "idle after nudge without PR", actions };
@@ -6016,7 +6639,10 @@ var runDeliver = async (input) => {
6016
6639
  const orca = orcaOptions(config);
6017
6640
  const [accountList, agentHooks] = await Promise.all([orcaAccountList(input.runner, orca).catch(() => ({})), orcaAgentHooks(input.runner, orca).catch(() => ({}))]);
6018
6641
  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 });
6019
- const reviewer = rankModels(config, "reviewer", providers)[0] ?? null;
6642
+ const availableIds = providers.filter((provider) => provider.available).map((provider) => provider.id);
6643
+ const catalogExtras = async (role) => config.models.routing.mode === "catalog" ? resolveCatalogCandidates({ config, role, availableProviderIds: availableIds, runner: input.runner, stateDir: loaded.stateDir, env: input.env, now: now4 }) : Promise.resolve([]);
6644
+ const reviewer = rankModels(config, "reviewer", providers, await catalogExtras("reviewer"))[0] ?? null;
6645
+ const builder = rankModels(config, "builder", providers, await catalogExtras("builder"))[0] ?? null;
6020
6646
  let env = input.env ?? process.env;
6021
6647
  if (!env["GITHUB_TOKEN"] && !env["GH_TOKEN"]) {
6022
6648
  try {
@@ -6027,7 +6653,7 @@ var runDeliver = async (input) => {
6027
6653
  }
6028
6654
  const reviewDeadlineMs = input.budgetMs ? Math.max(6e4, Math.min(config.delivery.review.deadlineMs, input.budgetMs - 9e4)) : config.delivery.review.deadlineMs;
6029
6655
  if (reviewDeadlineMs < config.delivery.review.deadlineMs) notes.push(`review deadline capped to ${Math.round(reviewDeadlineMs / 1e3)}s to fit the stage budget`);
6030
- const ctx = { loaded, config, runner: input.runner, now: now4, dryRun, reviewer, env, ...input.assumeIdle === void 0 ? {} : { assumeIdle: input.assumeIdle }, notes, reviewDeadlineMs };
6656
+ const ctx = { loaded, config, runner: input.runner, now: now4, dryRun, reviewer, builder, providers, env, ...input.assumeIdle === void 0 ? {} : { assumeIdle: input.assumeIdle }, notes, reviewDeadlineMs };
6031
6657
  const ledger = createDispatchLedger(loaded.stateDir);
6032
6658
  const leases = new Map(ledger.active().map((lease) => [lease.issue, lease]));
6033
6659
  const results = [];
@@ -7114,6 +7740,6 @@ var watchDeliveries = async (input) => {
7114
7740
  };
7115
7741
  var formatWatchEvent = (event2) => `${event2.kind}: ${event2.issue} \xB7 ${event2.message}`;
7116
7742
 
7117
- export { AGENT_REGISTRY_SCHEMA_VERSION, ARTIFACT_SCHEMA_VERSION, ARTIFACT_TYPES, ASSURANCE_LEVELS, AgentRegistryEntrySchema, AgentRegistrySchema, BENCHMARK_SCHEMA_VERSION, BLOCK_STATUSES, CAPABILITY_KINDS, CAPABILITY_MANIFEST_SCHEMA_VERSION, COMPATIBILITY_COMPONENTS, COMPATIBILITY_SCHEMA_VERSION, CONTEXT_PROVIDER_SLOT, CONTRACT_CLOSE, CONTRACT_OPEN, CONTRACT_SCHEMA_VERSION, ContractOutcomeSchema, EVAL_COMPONENTS, EVAL_LAYERS, EVAL_MANIFEST_SCHEMA_VERSION, EVENT_LOG_GENESIS, EVIDENCE_BUNDLE_SCHEMA_VERSION, FileArtifactStore, FileEventStore, HARNESS_ERROR_CATALOG, HARNESS_ERROR_CODES, HARNESS_EVENT_ENVELOPE_SCHEMA_VERSION, HARNESS_EVENT_SCHEMA_VERSION, HARNESS_EVENT_TYPES, HARNESS_PLUGIN_API_VERSION, HARNESS_REPO_URL, HarnessError, IMPROVEMENT_CYCLE_STEPS, LEARNING_STATUSES, LEGAL_TRANSITIONS, LOOP_CONFIG_FILE, LOOP_CONFIG_SCHEMA_VERSION, LOOP_LOCAL_CONFIG_FILE, LOOP_STAGES, LoopConfigSchema, MEMORY_SCOPES, MODEL_ROLES, PHASE_DECISIONS, PHASE_EFFECTS, PHASE_EFFECT_ACTIONS, PHASE_MODES, PR_FIELDS, QUALITY_DIMENSIONS, REVIEW_SEVERITIES, STATES, TaskContractSchema, WIP_STATES, activeCooldowns, adaptiveConcurrency, appendLoopEvent, approveRun, approvedDecision, artifactDigest, artifactFilePath, artifactIsFresh, artifactMarkdownPath, assertHuman, assessAcceptance, assessAgentEval, assessBlock, assessChecks, assessCompatibility, assessContract, assessDiscovery, assessImprovementCycle, assessIntegration, assessPilot, assessPreflight, assessProduction, assessQaTransition, assessSlots, assessWip, assessWorktreeCleanup, atLeast, authStatusFor, authorizeRun, automationName, automationPrompt, automationSpecs, availableMemoryBytes, benchmarkRuns, branchFor, buildDebriefReport, buildListIssuesArgv, buildRetroReport, buildReviewArgv, buildSuggestions, busyIssues, cancelRun, classifyFailure, classifyHarnessError, classifyProviderFailure, classifyWatchEvent, classifyWatchPhase, cleanTaskArtifacts, clearProviderCooldown, compareOptimization, compareVersions, composePullRequest, contractIsFresh, contractPath, cooldownPath, cooldownUntil, countRunningWorkers, createArgvRagContextProvider, createArtifactEnvelope, createCapabilityManifest, createCodingAgentAdapter, createCompatibilityManifest, createConfiguredToolRuntime, createDispatchLedger, createDocBridgeContextProvider, createDockerToolRuntime, createEvalManifest, createFileMemoryAdapter, createFileMemoryKvStore, createHarnessEventEnvelope, createInMemoryMemoryAdapter, createKvMemoryAdapter, createLinearTrackingAdapter, createLlmCache, createLlmCacheKey, createMachineMonitor, createMcpToolBridge, createModelPolicy, createOrcaDispatchPlan, createOrcaLifecycleProjection, createPhaseArtifact, createPhaseProfile, createPluginRegistry, createPluginSlot, createPolicyGate, createProcessRunner, createProcessToolRuntime, createPullRequestApproval, createQualityMatrix, createRagContextProvider, createRichIO, createSessionRecorder, createStatusSnapshot, createToolRuntime, createTrackingAdapter, createTrackingTransition, deliveryStatePath, detectProviders, dispatchRecordPath, evaluateWatchdog, executePhaseProfile, exportEvidenceBundle, fetchLinearIssue, fetchLinearQueue, fetchTeamMembers, filterAndOrderQueue, findExecutable, formatWatchEvent, gatherLoopState, generateContract, githubComment, githubCommentArgv, githubCommentExists, githubMerge, githubMergeArgv, githubOpenPullRequests, githubPullRequest, githubPullRequestsForBranch, hasLocalConfig, hashContextSnapshot, hashContextSnapshots, hashMcpArgs, inspectDocBridgeIndex, inspectEventLogLock, installLoopAutomations, installPreflight, isDiscoveryCurrent, isWsl, launchWorkerTerminal, learningToMemoryRecord, learningsPath, linearAttach, linearAttachArgv, linearCommentAdd, linearCommentAddArgv, linearLabelAdd, linearLabelArgv, linearLabelRemove, linearStatusSet, linearStatusSetArgv, listDispatched, loadAgentRegistry, loadBenchmarkManifest, loadConfig, loadLatestRun, loadLoopConfig, localConfigPath, loopStatus, markProviderExhausted, memoryDigestOf, mergeLoopConfig, modelFor, normalizeReason, openLoopMemory, orcaAccountList, orcaAgentHooks, orcaAutomationCreateArgv, orcaAutomationEditArgv, orcaAutomationRemove, orcaAutomationRun, orcaAutomationRuns, orcaAutomationsList, orcaJson, orcaStatus, orcaTerminalCreate, orcaTerminalList, orcaTerminalScreen, orcaTerminalSend, orcaTerminalWait, orcaVersion, orcaWorktreeCreate, orcaWorktreeRemove, orcaWorktreeSet, orcaWorktreeSetArgv, orcaWorktrees, parseAgentRegistryText, parseAutomationRuns, parseContractOutput, parseJsonEnvelope, parseLinearIssueDetail, parseLinearIssues, parseLoopConfigText, parseMemInfo, parseModelRef, parseOrcaAgentHooks, parseOrcaAutomations, parseOrcaSendReceipt, parseOrcaStatus, parseOrcaTerminals, parseOrcaVersion, parseOrcaWorktreeCreate, parseOrcaWorktrees, parseProviderUsage, parsePullRequest, parseRagQueryOutput, parseRetro, parseReviewResult, parseSince, parseTeamMembers, parseUsageWindows, parseVmStat, planFilePreflight, planMemoryContext, planPhaseProfile, planRun, precheckCommand, precheckDeliver, precheckTick, preferMemoryOverDocBridge, promoteLearnings, promoteLearningsToMemory, promptLocalConfig, providerIdentity, providerSpecs, rankModels, readArtifactFile, readContextSnapshots, readCooldowns, readDeliveryState, readDispatchRecord, readEvidenceTrustStore, readLearningsLedger, readLoopEvents, readStoredContract, reconcileRun, recordBenchmarkObservation, recoverEventLogLock, recoveryDelayMs, renderArtifactMarkdown, renderContractPrompt, renderDebriefMarkdown, renderFindingsForWorker, renderHeadlessArgv, renderLocalConfig, renderRetroMarkdown, renderTuiCommand, renderWorkerBrief, resolveAgentForRole, resolveDocContext, resumeStateFromArtifacts, retroLearnings, retryRun, routeAllRoles, runAdversarialReview, runAgentEval, runCodeReview, runDeliver, runEvalBattery, runGuidedInstall, runLoopDoctor, runRetroStage, runTick, runWithRecovery, runWorkflow, sampleMachine, selectMemoryForPrompt, selectModel, selectRuntime, severityRank, shellQuote, snapshotWatchTargets, startRun, summarizeMachine, tiersFor, touchesProtectedPaths, transition, uninstallLoopAutomations, unknownTelemetry, untrusted, upsertProposedLearnings, validateAdapterMetadata, validateArtifactEnvelope, validateBenchmarkManifest, validateBlockManifest, validateCacheableOperation, validateCapabilityManifest, validateCompatibilityManifest, validateConfig, validateContextSnapshot, validateContextSnapshots, validateEvalManifest, validateHarnessErrorClassification, validateHarnessEventEnvelope, validateLoopConfig, validateMemoryRecord, validateOptimizationObservation, validatePhaseTelemetry, validateSafeCommand, validateStatusSnapshot, verifyEvidenceBundle, verifyPullRequestApproval, verifyRun, watchDeliveries, worktreeNameFor, writeIdFor, writeLearningsLedger, writeLocalConfig, writeStoredContract };
7743
+ export { AGENT_REGISTRY_SCHEMA_VERSION, ARTIFACT_SCHEMA_VERSION, ARTIFACT_TYPES, ASSURANCE_LEVELS, AgentRegistryEntrySchema, AgentRegistrySchema, BENCHMARK_SCHEMA_VERSION, BLOCK_STATUSES, CAPABILITY_KINDS, CAPABILITY_MANIFEST_SCHEMA_VERSION, COMPATIBILITY_COMPONENTS, COMPATIBILITY_SCHEMA_VERSION, CONTEXT_PROVIDER_SLOT, CONTRACT_CLOSE, CONTRACT_OPEN, CONTRACT_SCHEMA_VERSION, ContractOutcomeSchema, EVAL_COMPONENTS, EVAL_LAYERS, EVAL_MANIFEST_SCHEMA_VERSION, EVENT_LOG_GENESIS, EVIDENCE_BUNDLE_SCHEMA_VERSION, FileArtifactStore, FileEventStore, HARNESS_ERROR_CATALOG, HARNESS_ERROR_CODES, HARNESS_EVENT_ENVELOPE_SCHEMA_VERSION, HARNESS_EVENT_SCHEMA_VERSION, HARNESS_EVENT_TYPES, HARNESS_PLUGIN_API_VERSION, HARNESS_REPO_URL, HarnessError, IMPROVEMENT_CYCLE_STEPS, LEARNING_STATUSES, LEGAL_TRANSITIONS, LOOP_CONFIG_FILE, LOOP_CONFIG_SCHEMA_VERSION, LOOP_LOCAL_CONFIG_FILE, LOOP_STAGES, LoopConfigSchema, MEMORY_SCOPES, MODEL_ROLES, PHASE_DECISIONS, PHASE_EFFECTS, PHASE_EFFECT_ACTIONS, PHASE_MODES, PR_FIELDS, QUALITY_DIMENSIONS, REVIEW_SEVERITIES, STATES, TaskContractSchema, WIP_STATES, activeCooldowns, adaptiveConcurrency, appendLoopEvent, approveRun, approvedDecision, artifactDigest, artifactFilePath, artifactIsFresh, artifactMarkdownPath, assertHuman, assessAcceptance, assessAgentEval, assessBlock, assessChecks, assessCompatibility, assessContract, assessDiscovery, assessImprovementCycle, assessIntegration, assessPilot, assessPreflight, assessProduction, assessQaTransition, assessSlots, assessWip, assessWorktreeCleanup, atLeast, authStatusFor, authorizeRun, automationName, automationPrompt, automationSpecs, availableMemoryBytes, benchmarkRuns, branchFor, buildDebriefReport, buildListIssuesArgv, buildRetroReport, buildReviewArgv, buildSuggestions, busyIssues, cancelRun, classifyFailure, classifyHarnessError, classifyProviderFailure, classifyWatchEvent, classifyWatchPhase, cleanTaskArtifacts, clearProviderCooldown, compareOptimization, compareVersions, composePullRequest, contractIsFresh, contractPath, cooldownPath, cooldownUntil, countRunningWorkers, createArgvRagContextProvider, createArtifactEnvelope, createCapabilityManifest, createCodingAgentAdapter, createCompatibilityManifest, createConfiguredToolRuntime, createDispatchLedger, createDocBridgeContextProvider, createDockerToolRuntime, createEvalManifest, createFileMemoryAdapter, createFileMemoryKvStore, createHarnessEventEnvelope, createInMemoryMemoryAdapter, createKvMemoryAdapter, createLinearTrackingAdapter, createLlmCache, createLlmCacheKey, createMachineMonitor, createMcpToolBridge, createModelPolicy, createOrcaDispatchPlan, createOrcaLifecycleProjection, createPhaseArtifact, createPhaseProfile, createPluginRegistry, createPluginSlot, createPolicyGate, createProcessRunner, createProcessToolRuntime, createPullRequestApproval, createQualityMatrix, createRagContextProvider, createRichIO, createSessionRecorder, createStatusSnapshot, createToolRuntime, createTrackingAdapter, createTrackingTransition, deliveryStatePath, detectProviders, dispatchRecordPath, evaluateWatchdog, executePhaseProfile, exportEvidenceBundle, fetchArtificialAnalysisModels, fetchLinearIssue, fetchLinearQueue, fetchTeamMembers, filterAndOrderQueue, findExecutable, formatWatchEvent, gatherLoopState, generateContract, githubComment, githubCommentArgv, githubCommentExists, githubMerge, githubMergeArgv, githubOpenPullRequests, githubPullRequest, githubPullRequestsForBranch, hasLocalConfig, hashContextSnapshot, hashContextSnapshots, hashMcpArgs, inspectDocBridgeIndex, inspectEventLogLock, installLoopAutomations, installPreflight, isDiscoveryCurrent, isWsl, launchWorkerTerminal, learningToMemoryRecord, learningsPath, linearAttach, linearAttachArgv, linearCommentAdd, linearCommentAddArgv, linearLabelAdd, linearLabelArgv, linearLabelRemove, linearStatusSet, linearStatusSetArgv, listCliModels, listDispatched, loadAgentRegistry, loadAliases, loadBenchmarkManifest, loadBuiltinCatalog, loadConfig, loadLatestRun, loadLoopConfig, localConfigPath, loopStatus, markProviderExhausted, memoryDigestOf, mergeLoopConfig, modelFor, normalizeReason, openLoopMemory, orcaAccountList, orcaAgentHooks, orcaAutomationCreateArgv, orcaAutomationEditArgv, orcaAutomationRemove, orcaAutomationRun, orcaAutomationRuns, orcaAutomationsList, orcaJson, orcaStatus, orcaTerminalCreate, orcaTerminalList, orcaTerminalScreen, orcaTerminalSend, orcaTerminalWait, orcaVersion, orcaWorktreeCreate, orcaWorktreeRemove, orcaWorktreeSet, orcaWorktreeSetArgv, orcaWorktrees, parseAgentRegistryText, parseArtificialAnalysisPayload, parseAutomationRuns, parseContractOutput, parseGrokModelsOutput, parseJsonEnvelope, parseLinearIssueDetail, parseLinearIssues, parseLoopConfigText, parseMemInfo, parseModelRef, parseOrcaAgentHooks, parseOrcaAutomations, parseOrcaSendReceipt, parseOrcaStatus, parseOrcaTerminals, parseOrcaVersion, parseOrcaWorktreeCreate, parseOrcaWorktrees, parseProviderUsage, parsePullRequest, parseRagQueryOutput, parseRetro, parseReviewResult, parseSince, parseTeamMembers, parseUsageWindows, parseVmStat, planFilePreflight, planMemoryContext, planPhaseProfile, planRun, precheckCommand, precheckDeliver, precheckTick, preferMemoryOverDocBridge, promoteLearnings, promoteLearningsToMemory, promptLocalConfig, providerIdentity, providerSpecs, rankModels, readAaCache, readArtifactFile, readContextSnapshots, readCooldowns, readDeliveryState, readDispatchRecord, readEvidenceTrustStore, readLearningsLedger, readLoopEvents, readStoredContract, reconcileRun, recordBenchmarkObservation, recoverEventLogLock, recoveryDelayMs, remainingUsagePercent, renderArtifactMarkdown, renderContractPrompt, renderDebriefMarkdown, renderFindingsForWorker, renderHandoffBrief, renderHeadlessArgv, renderLocalConfig, renderRetroMarkdown, renderTuiCommand, renderWorkerBrief, resolveAgentForRole, resolveAlias, resolveCatalogCandidates, resolveDocContext, resumeStateFromArtifacts, retroLearnings, retryRun, routeAllRoles, runAdversarialReview, runAgentEval, runCodeReview, runDeliver, runEvalBattery, runGuidedInstall, runLoopDoctor, runRetroStage, runTick, runWithRecovery, runWorkflow, sampleMachine, selectMemoryForPrompt, selectModel, selectRuntime, severityRank, shellQuote, snapshotWatchTargets, startRun, summarizeMachine, tiersFor, touchesProtectedPaths, transition, undeclaredOrcaProviders, uninstallLoopAutomations, unknownTelemetry, untrusted, upsertProposedLearnings, usageRankTuple, validateAdapterMetadata, validateArtifactEnvelope, validateBenchmarkManifest, validateBlockManifest, validateCacheableOperation, validateCapabilityManifest, validateCompatibilityManifest, validateConfig, validateContextSnapshot, validateContextSnapshots, validateEvalManifest, validateHarnessErrorClassification, validateHarnessEventEnvelope, validateLoopConfig, validateMemoryRecord, validateOptimizationObservation, validatePhaseTelemetry, validateSafeCommand, validateStatusSnapshot, verifyEvidenceBundle, verifyPullRequestApproval, verifyRun, watchDeliveries, worktreeNameFor, writeAaCache, writeDispatchRecord, writeIdFor, writeLearningsLedger, writeLocalConfig, writeStoredContract };
7118
7744
  //# sourceMappingURL=index.js.map
7119
7745
  //# sourceMappingURL=index.js.map