@agentskit/harness 0.10.0 → 0.12.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
@@ -1234,11 +1234,34 @@ var validateCapabilityManifest = (value) => {
1234
1234
  var index = (root, indexPath) => JSON.parse(readFileSync(resolve(root, indexPath), "utf8"));
1235
1235
  var text = (entry) => [entry.id, entry.type, entry.title, entry.path, entry.description, entry.body, ...Array.isArray(entry.tags) ? entry.tags : []].filter((value) => typeof value === "string").join(" ").toLowerCase();
1236
1236
  var sourceHash = (document) => typeof document.contentHash === "string" && document.contentHash.length > 0 ? document.contentHash : hashJson(document);
1237
+ var tokenSeparator = /[^\p{L}\p{N}@/_-]+/gu;
1238
+ var tokenize = (value) => value.toLowerCase().split(tokenSeparator).filter((token) => token.length >= 2);
1239
+ var containsToken = (value, token) => {
1240
+ if (/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}]/u.test(token)) return value.includes(token);
1241
+ const escaped = token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1242
+ return new RegExp(`(?:^|[^\\p{L}\\p{N}])${escaped}(?:s|es)?(?:[^\\p{L}\\p{N}]|$)`, "u").test(value);
1243
+ };
1244
+ var score = (value, query) => tokenize(query).reduce((total, token) => total + (containsToken(value, token) ? token.length : 0), 0);
1237
1245
  var matches = (entry, query) => {
1238
- const needle = query.query.trim().toLowerCase();
1239
- const scopes = query.scope?.map((scope) => scope.toLowerCase()) ?? [];
1246
+ const needle = query.query.trim();
1247
+ if (!needle) return 0;
1240
1248
  const value = text(entry);
1241
- return Boolean(needle && value.includes(needle) && (scopes.length === 0 || scopes.some((scope) => value.includes(scope))));
1249
+ if (query.scope?.length && !query.scope.some((scope) => containsToken(value, scope.toLowerCase()))) return 0;
1250
+ return score(value, needle);
1251
+ };
1252
+ var ownershipEntries = (document) => {
1253
+ if (typeof document.lookup !== "object" || document.lookup === null || Array.isArray(document.lookup)) return [];
1254
+ const ownership = document.lookup.ownership;
1255
+ if (typeof ownership !== "object" || ownership === null || Array.isArray(ownership)) return [];
1256
+ return Object.entries(ownership).flatMap(([id2, value]) => {
1257
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return [];
1258
+ const owner = value;
1259
+ const path = typeof owner["agentDoc"] === "string" ? owner["agentDoc"] : owner["path"];
1260
+ if (!path) return [];
1261
+ const description = typeof owner["purpose"] === "string" ? owner["purpose"] : void 0;
1262
+ const body3 = [owner["purpose"], owner["group"], owner["layer"], owner["agentDoc"], owner["humanDoc"]].filter((item) => typeof item === "string").join(" ");
1263
+ return [{ id: typeof owner["id"] === "string" ? owner["id"] : id2, type: "ownership", path, ...description ? { description } : {}, ...body3 ? { body: body3 } : {} }];
1264
+ });
1242
1265
  };
1243
1266
  var inspectDocBridgeIndex = (root, indexPath = ".doc-bridge/index.json", now4 = Date.now()) => {
1244
1267
  const path = resolve(root, indexPath);
@@ -1253,15 +1276,30 @@ var inspectDocBridgeIndex = (root, indexPath = ".doc-bridge/index.json", now4 =
1253
1276
  return { present: true, path, contentHash: null, mtimeMs: null, ageHours: null, error: error instanceof Error ? error.message : String(error) };
1254
1277
  }
1255
1278
  };
1256
- var createDocBridgeContextProvider = ({ root, indexPath = ".doc-bridge/index.json" }) => ({
1279
+ var createDocBridgeContextProvider = ({ root, indexPath = ".doc-bridge/index.json", maxAgeHours, now: now4 = Date.now }) => ({
1257
1280
  id: "doc-bridge",
1258
- version: "1.0.0",
1281
+ version: "1.1.0",
1259
1282
  resolve: async (query) => {
1260
1283
  const started = Date.now();
1284
+ const ageBudget = maxAgeHours ?? 0;
1285
+ const inspection = ageBudget > 0 ? inspectDocBridgeIndex(root, indexPath, now4()) : null;
1286
+ if (inspection?.error) throw new Error(`Doc Bridge index is unreadable: ${inspection.error}`);
1287
+ if (inspection?.ageHours !== null && inspection?.ageHours !== void 0 && inspection.ageHours > ageBudget) {
1288
+ throw new Error(`Doc Bridge index is ${inspection.ageHours.toFixed(1)}h old; refresh it before resolving context.`);
1289
+ }
1261
1290
  const document = index(root, indexPath);
1262
1291
  const contentHash = sourceHash(document);
1263
- const entries = Array.isArray(document.knowledge) ? document.knowledge.filter((value) => typeof value === "object" && value !== null && !Array.isArray(value)).filter((entry) => matches(entry, query)).sort((left, right) => String(left.id ?? "").localeCompare(String(right.id ?? ""))).slice(0, 8) : [];
1264
- const references = entries.flatMap((entry) => typeof entry.id === "string" && typeof entry.path === "string" ? [{ id: entry.id, uri: `doc-bridge://${entry.path}`, ...typeof entry.title === "string" ? { title: entry.title } : {}, contentHash: typeof entry.contentHash === "string" ? entry.contentHash : contentHash, relevance: 1 }] : []);
1292
+ const knowledge = Array.isArray(document.knowledge) ? document.knowledge.filter((value) => typeof value === "object" && value !== null && !Array.isArray(value)) : [];
1293
+ const ranked = [...knowledge, ...ownershipEntries(document)].map((entry) => ({ entry, score: matches(entry, query) })).filter(({ score: entryScore }) => entryScore > 0);
1294
+ const byPath = /* @__PURE__ */ new Map();
1295
+ for (const candidate of ranked) {
1296
+ const path = typeof candidate.entry.path === "string" ? candidate.entry.path : String(candidate.entry.id ?? "");
1297
+ const current = byPath.get(path);
1298
+ if (!current || candidate.score > current.score || candidate.score === current.score && candidate.entry.type === "ownership" && current.entry.type !== "ownership") byPath.set(path, candidate);
1299
+ }
1300
+ const entries = [...byPath.values()].sort((left, right) => right.score - left.score || String(left.entry.id ?? "").localeCompare(String(right.entry.id ?? ""))).slice(0, 8);
1301
+ const maxScore = entries[0]?.score ?? 1;
1302
+ const references = entries.flatMap(({ entry, score: entryScore }) => typeof entry.id === "string" && typeof entry.path === "string" ? [{ id: entry.id, uri: `doc-bridge://${entry.path}`, ...typeof entry.title === "string" ? { title: entry.title } : {}, contentHash: typeof entry.contentHash === "string" ? entry.contentHash : contentHash, relevance: entryScore / maxScore }] : []);
1265
1303
  const telemetry = { status: "measured", durationMs: Date.now() - started, contextReferences: references.length, contextCostTokens: Math.max(1, Math.ceil(JSON.stringify(references).length / 4)) };
1266
1304
  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
1305
  }
@@ -1970,7 +2008,7 @@ var digest4 = (value, label) => {
1970
2008
  if (!/^[a-f0-9]{64}$/.test(result)) fail(`${label} must be a lowercase SHA-256 digest.`, "INVALID_INPUT");
1971
2009
  return result;
1972
2010
  };
1973
- var score = (value, label) => {
2011
+ var score2 = (value, label) => {
1974
2012
  const numeric = typeof value === "number" ? value : Number.NaN;
1975
2013
  if (!Number.isFinite(numeric) || numeric < 0 || numeric > 100) fail(`${label} must be a number between 0 and 100.`, "INVALID_INPUT");
1976
2014
  return numeric;
@@ -1988,7 +2026,7 @@ var validateCase = (value, index2) => {
1988
2026
  return component2;
1989
2027
  });
1990
2028
  if (new Set(normalizedComponents).size !== normalizedComponents.length) fail(`cases[${index2}].components must not contain duplicates.`, "INVALID_INPUT");
1991
- const baselineScore = candidate["baselineScore"] === void 0 ? void 0 : score(candidate["baselineScore"], `cases[${index2}].baselineScore`);
2029
+ const baselineScore = candidate["baselineScore"] === void 0 ? void 0 : score2(candidate["baselineScore"], `cases[${index2}].baselineScore`);
1992
2030
  return {
1993
2031
  id: nonEmpty4(candidate["id"], `cases[${index2}].id`),
1994
2032
  layer,
@@ -2026,7 +2064,7 @@ var manifestBody2 = (value) => {
2026
2064
  name: nonEmpty4(value["name"], "name"),
2027
2065
  cases,
2028
2066
  graders,
2029
- thresholds: { subjectiveQuality: score(thresholds2["subjectiveQuality"] ?? 80, "thresholds.subjectiveQuality"), maxRegression: score(thresholds2["maxRegression"] ?? 5, "thresholds.maxRegression") },
2067
+ thresholds: { subjectiveQuality: score2(thresholds2["subjectiveQuality"] ?? 80, "thresholds.subjectiveQuality"), maxRegression: score2(thresholds2["maxRegression"] ?? 5, "thresholds.maxRegression") },
2030
2068
  repetitions,
2031
2069
  provider: nonEmpty4(value["provider"], "provider"),
2032
2070
  model: nonEmpty4(value["model"], "model"),
@@ -2251,6 +2289,14 @@ var createKvMemoryAdapter = (store, options = {}) => {
2251
2289
  const scopeMatch = record3.scope === "global" || (record3.scope === "issue" ? Boolean(issueId && record3.source.includes(issueId)) : Boolean(project && record3.source.includes(project)));
2252
2290
  return scopeMatch && (!query || `${record3.summary} ${record3.source}`.toLowerCase().includes(query));
2253
2291
  };
2292
+ let allRecordsCache = null;
2293
+ const allRecords = async () => {
2294
+ if (allRecordsCache) return allRecordsCache;
2295
+ const ids = await store.get(indexKey);
2296
+ const records = Array.isArray(ids) ? await Promise.all(ids.filter((id2) => typeof id2 === "string").map((id2) => store.get(`agentskit-harness:memory:${id2}`))) : [];
2297
+ allRecordsCache = records.filter((record3) => Boolean(record3 && typeof record3 === "object" && record3.approved === true));
2298
+ return allRecordsCache;
2299
+ };
2254
2300
  return {
2255
2301
  id: options.id ?? "agentskit-kv",
2256
2302
  version: options.version ?? "1",
@@ -2262,13 +2308,13 @@ var createKvMemoryAdapter = (store, options = {}) => {
2262
2308
  const index2 = Array.isArray(ids) ? ids.filter((id2) => typeof id2 === "string") : [];
2263
2309
  if (!index2.includes(valid.id)) await store.set(indexKey, [...index2, valid.id].sort());
2264
2310
  await store.set(`agentskit-harness:memory:${valid.id}`, valid);
2311
+ allRecordsCache = null;
2265
2312
  writes += 1;
2266
2313
  },
2267
2314
  async recall({ query, issueId, project, sourceRevision }) {
2268
2315
  reads += 1;
2269
- const ids = await store.get(indexKey);
2270
- const records = Array.isArray(ids) ? await Promise.all(ids.filter((id2) => typeof id2 === "string").map((id2) => store.get(`agentskit-harness:memory:${id2}`))) : [];
2271
- const hits = records.filter((record3) => Boolean(record3 && typeof record3 === "object" && record3.approved === true)).filter((record3) => matches2(record3, query.trim().toLowerCase(), issueId, project)).map((record3) => ({ record: record3, relevant: true, stale: sourceRevision !== void 0 && record3.sourceRevision !== sourceRevision }));
2316
+ const records = await allRecords();
2317
+ const hits = records.filter((record3) => matches2(record3, query.trim().toLowerCase(), issueId, project)).map((record3) => ({ record: record3, relevant: true, stale: sourceRevision !== void 0 && record3.sourceRevision !== sourceRevision }));
2272
2318
  relevantHits += hits.length;
2273
2319
  staleHits += hits.filter((hit) => hit.stale).length;
2274
2320
  return hits;
@@ -2708,12 +2754,12 @@ var validatePhaseTelemetry = (value) => {
2708
2754
  ...machine ? { machine } : {}
2709
2755
  };
2710
2756
  };
2711
- var average = (values) => values.length ? Number((values.reduce((sum, value) => sum + value, 0) / values.length).toFixed(2)) : null;
2712
- var score2 = (value, source, baseline = null) => ({ score: value === null ? null : Math.max(0, Math.min(100, Number(value.toFixed(2)))), status: value === null ? "unknown" : "measured", baselineDelta: value === null || baseline === null ? null : Number((value - baseline).toFixed(2)), source });
2757
+ var average = (values) => values.length ? Number((values.reduce((sum2, value) => sum2 + value, 0) / values.length).toFixed(2)) : null;
2758
+ var score3 = (value, source, baseline = null) => ({ score: value === null ? null : Math.max(0, Math.min(100, Number(value.toFixed(2)))), status: value === null ? "unknown" : "measured", baselineDelta: value === null || baseline === null ? null : Number((value - baseline).toFixed(2)), source });
2713
2759
  var evaluateWatchdog = ({ phases, budget }) => {
2714
2760
  const blockers = [];
2715
- const duration5 = phases.every((phase2) => phase2.durationMs !== void 0) ? phases.reduce((sum, phase2) => sum + (phase2.durationMs ?? 0), 0) : void 0;
2716
- const totalTokens = phases.every((phase2) => phase2.tokens?.inputTokens !== void 0 && phase2.tokens.outputTokens !== void 0) ? phases.reduce((sum, phase2) => sum + (phase2.tokens?.inputTokens ?? 0) + (phase2.tokens?.outputTokens ?? 0), 0) : void 0;
2761
+ const duration5 = phases.every((phase2) => phase2.durationMs !== void 0) ? phases.reduce((sum2, phase2) => sum2 + (phase2.durationMs ?? 0), 0) : void 0;
2762
+ const totalTokens = phases.every((phase2) => phase2.tokens?.inputTokens !== void 0 && phase2.tokens.outputTokens !== void 0) ? phases.reduce((sum2, phase2) => sum2 + (phase2.tokens?.inputTokens ?? 0) + (phase2.tokens?.outputTokens ?? 0), 0) : void 0;
2717
2763
  if (budget.maxDurationMs !== void 0 && duration5 !== void 0 && duration5 > budget.maxDurationMs) blockers.push({ class: "budget", reason: `Duration budget exceeded: ${duration5}ms > ${budget.maxDurationMs}ms.` });
2718
2764
  if (budget.maxTotalTokens !== void 0 && totalTokens !== void 0 && totalTokens > budget.maxTotalTokens) blockers.push({ class: "budget", reason: `Token budget exceeded: ${totalTokens} > ${budget.maxTotalTokens}.` });
2719
2765
  for (const phase2 of phases) {
@@ -2729,16 +2775,16 @@ var createQualityMatrix = ({ phases, baseline, budget = {} }) => {
2729
2775
  const priorDurations = prior.flatMap((phase2) => phase2.durationMs === void 0 ? [] : [phase2.durationMs]);
2730
2776
  const currentTokens = current.flatMap((phase2) => phase2.tokens?.inputTokens !== void 0 && phase2.tokens.outputTokens !== void 0 ? [phase2.tokens.inputTokens + phase2.tokens.outputTokens] : []);
2731
2777
  const priorTokens = prior.flatMap((phase2) => phase2.tokens?.inputTokens !== void 0 && phase2.tokens.outputTokens !== void 0 ? [phase2.tokens.inputTokens + phase2.tokens.outputTokens] : []);
2732
- const correctness = score2(average(current.map((phase2) => phase2.evidenceCoverage === void 0 ? 0 : phase2.evidenceCoverage * 100)), "mean evidence coverage");
2733
- const completeness = score2(current.length ? current.filter((phase2) => phase2.outcome === "pass").length / current.length * 100 : null, "passed phases / total phases");
2734
- const speed = score2(currentDurations.length && priorDurations.length ? average(priorDurations) / Math.max(1, average(currentDurations)) * 100 : null, "baseline duration / current duration", 100);
2735
- const cost = score2(currentTokens.length && priorTokens.length ? average(priorTokens) / Math.max(1, average(currentTokens)) * 100 : null, "baseline tokens / current tokens", 100);
2778
+ const correctness = score3(average(current.map((phase2) => phase2.evidenceCoverage === void 0 ? 0 : phase2.evidenceCoverage * 100)), "mean evidence coverage");
2779
+ const completeness = score3(current.length ? current.filter((phase2) => phase2.outcome === "pass").length / current.length * 100 : null, "passed phases / total phases");
2780
+ const speed = score3(currentDurations.length && priorDurations.length ? average(priorDurations) / Math.max(1, average(currentDurations)) * 100 : null, "baseline duration / current duration", 100);
2781
+ const cost = score3(currentTokens.length && priorTokens.length ? average(priorTokens) / Math.max(1, average(currentTokens)) * 100 : null, "baseline tokens / current tokens", 100);
2736
2782
  const resourceValues = current.flatMap((phase2) => phase2.machine?.cpuPercent !== void 0 && phase2.machine.memoryUsedPercent !== void 0 ? [100 - Math.max(phase2.machine.cpuPercent, phase2.machine.memoryUsedPercent)] : []);
2737
- const resource = score2(average(resourceValues), "100 - max(cpu%, memory%)");
2738
- const reliability = score2(current.length ? current.filter((phase2) => phase2.outcome === "pass").length / current.length * 100 : null, "passed phases / total phases");
2783
+ const resource = score3(average(resourceValues), "100 - max(cpu%, memory%)");
2784
+ const reliability = score3(current.length ? current.filter((phase2) => phase2.outcome === "pass").length / current.length * 100 : null, "passed phases / total phases");
2739
2785
  const dimensions = { correctness, completeness, speed, cost, resource, reliability };
2740
2786
  const measured = Object.values(dimensions).filter((item) => item.score !== null).map((item) => item.score);
2741
- const overall = score2(average(measured), "mean of measured dimensions");
2787
+ const overall = score3(average(measured), "mean of measured dimensions");
2742
2788
  const unknownMetricCount = Object.values(dimensions).filter((item) => item.status === "unknown").length + current.filter((phase2) => phase2.durationMs === void 0 || phase2.tokens === void 0 || phase2.machine === void 0).length;
2743
2789
  const blockers = evaluateWatchdog({ phases: current, budget }).blockers;
2744
2790
  const body3 = { type: "agentskit-harness-quality-matrix", schemaVersion: 1, dimensions, overall, phaseCount: current.length, unknownMetricCount, blockers };
@@ -4179,6 +4225,21 @@ var orcaStatus = async (runner, options = {}) => parseOrcaStatus(await orcaJson(
4179
4225
  var orcaWorktrees = async (runner, options = {}) => parseOrcaWorktrees(await orcaJson(runner, ["worktree", "ps"], options));
4180
4226
  var orcaAgentHooks = async (runner, options = {}) => parseOrcaAgentHooks(await orcaJson(runner, ["agent", "hooks", "status"], options));
4181
4227
  var orcaAccountList = async (runner, options = {}) => orcaJson(runner, ["account", "list"], options);
4228
+ var orcaDiagnosticsMemory = async (runner, options = {}) => {
4229
+ try {
4230
+ const result = await orcaJson(runner, ["diagnostics", "memory"], options);
4231
+ if (!isRecord8(result)) return null;
4232
+ const host = isRecord8(result["host"]) ? result["host"] : {};
4233
+ const availableBytes = host["availableMemory"];
4234
+ if (typeof availableBytes !== "number" || !Number.isFinite(availableBytes) || availableBytes <= 0) return null;
4235
+ const totalBytes = typeof host["totalMemory"] === "number" ? host["totalMemory"] : null;
4236
+ const worktrees = Array.isArray(result["worktrees"]) ? result["worktrees"] : [];
4237
+ const agentRssSamples = worktrees.filter(isRecord8).flatMap((worktree) => Array.isArray(worktree["sessions"]) ? worktree["sessions"] : []).filter(isRecord8).map((session) => session["memory"]).filter((value) => typeof value === "number" && Number.isFinite(value) && value > 0);
4238
+ return { availableBytes, totalBytes, agentRssSamples };
4239
+ } catch {
4240
+ return null;
4241
+ }
4242
+ };
4182
4243
  var parseOrcaWorktreeCreate = (result) => {
4183
4244
  const record3 = isRecord8(result) ? result : {};
4184
4245
  const nested = isRecord8(record3["worktree"]) ? record3["worktree"] : record3;
@@ -4236,10 +4297,16 @@ var orcaTerminalCreate = async (runner, input, options = {}) => {
4236
4297
  };
4237
4298
  var parseOrcaSendReceipt = (result) => {
4238
4299
  const record3 = isRecord8(result) ? result : {};
4239
- const receipt = isRecord8(record3["receipt"]) ? record3["receipt"] : record3;
4240
- const stages = Array.isArray(receipt["stages"]) ? receipt["stages"].map((stage) => isRecord8(stage) ? str(stage["stage"], str(stage["name"])) : str(stage)).filter(Boolean) : [];
4241
- const accepted = receipt["accepted"] === false ? false : receipt["accepted"] === true || stages.includes("input_accepted") || (result === null || result === void 0 || Object.keys(record3).length === 0);
4242
- 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)) : [] };
4300
+ const send = isRecord8(record3["send"]) ? record3["send"] : null;
4301
+ const prompt = send && isRecord8(send["prompt"]) ? send["prompt"] : null;
4302
+ const receipt = isRecord8(record3["receipt"]) ? record3["receipt"] : send ?? record3;
4303
+ const rawStages = Array.isArray(receipt["stages"]) ? receipt["stages"] : prompt && Array.isArray(prompt["stages"]) ? prompt["stages"] : [];
4304
+ const stages = rawStages.map((stage) => isRecord8(stage) ? str(stage["stage"], str(stage["name"])) : str(stage)).filter(Boolean);
4305
+ const inputAccepted = stages.some((stage) => ["input_accepted", "input_queued", "prompt_accepted", "queued"].includes(stage.toLowerCase()));
4306
+ const acceptedValue = receipt["accepted"] ?? send?.["accepted"];
4307
+ const accepted = inputAccepted || acceptedValue === true || acceptedValue !== false && (result === null || result === void 0 || Object.keys(record3).length === 0);
4308
+ const warnings = Array.isArray(record3["warnings"]) ? record3["warnings"] : send && Array.isArray(send["warnings"]) ? send["warnings"] : [];
4309
+ return { accepted, requestId: str(receipt["requestId"], str(prompt?.["requestId"], str(record3["requestId"]))) || null, stages, warnings: warnings.map((warning) => isRecord8(warning) ? str(warning["message"], JSON.stringify(warning)) : str(warning)) };
4243
4310
  };
4244
4311
  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 }));
4245
4312
  var orcaTerminalWait = async (runner, input, options = {}) => {
@@ -4499,6 +4566,8 @@ var LoopConfigSchema = z.object({
4499
4566
  }).prefault({}),
4500
4567
  catalog: z.object({
4501
4568
  sources: z.array(z.enum(["cli", "artificial-analysis", "builtin"])).default(["cli", "builtin"]),
4569
+ /** How long a provider's CLI-discovered model list (e.g. `grok models`) is trusted before spawning the CLI again — it rarely changes between releases. */
4570
+ cliCacheHours: z.number().positive().default(6),
4502
4571
  artificialAnalysis: z.object({
4503
4572
  enabled: z.boolean().default(false),
4504
4573
  apiKeyEnv: nonEmpty5.default("ARTIFICIAL_ANALYSIS_API_KEY"),
@@ -4917,8 +4986,8 @@ var isWsl = (platform = process.platform, osRelease = release(), env = process.e
4917
4986
  var assessSlots = (input) => {
4918
4987
  const platform = input.platform ?? process.platform;
4919
4988
  const wsl = isWsl(platform, input.osRelease);
4920
- const freeBytes = input.freeBytes ?? availableMemoryBytes(platform);
4921
- const totalBytes = input.totalBytes ?? totalmem();
4989
+ const freeBytes = input.freeBytes ?? input.orcaMemory?.availableBytes ?? availableMemoryBytes(platform);
4990
+ const totalBytes = input.totalBytes ?? input.orcaMemory?.totalBytes ?? totalmem();
4922
4991
  const sample = input.sample ?? { ...sampleMachine(), memoryUsedPercent: Number(Math.max(0, Math.min(100, (1 - freeBytes / Math.max(1, totalBytes)) * 100)).toFixed(2)) };
4923
4992
  const freeRamGb = Number((freeBytes / 1024 ** 3).toFixed(2));
4924
4993
  const reasons = [];
@@ -4926,9 +4995,11 @@ var assessSlots = (input) => {
4926
4995
  const adaptive = adaptiveConcurrency(ceiling, sample, { warningPercent: input.machine.warningPercent, criticalPercent: input.machine.criticalPercent });
4927
4996
  if (adaptive < ceiling) reasons.push(`machine pressure capped concurrency at ${adaptive} (load ${sample.load1PerCpuPercent}%, memory ${sample.memoryUsedPercent}%)`);
4928
4997
  const reservedBytes = input.machine.minFreeRamGb * 1024 ** 3;
4929
- const perAgentBytes = input.machine.agentRssMb * 1024 ** 2;
4998
+ const measuredAgentBytes = input.orcaMemory?.agentRssSamples.length ? input.orcaMemory.agentRssSamples.reduce((total, value) => total + value, 0) / input.orcaMemory.agentRssSamples.length : null;
4999
+ const perAgentBytes = measuredAgentBytes ?? input.machine.agentRssMb * 1024 ** 2;
5000
+ const perAgentMb = Math.round(perAgentBytes / 1024 ** 2);
4930
5001
  const ramBound = Math.max(0, Math.floor((freeBytes - reservedBytes) / perAgentBytes)) + input.running;
4931
- if (ramBound < adaptive) reasons.push(`free RAM ${freeRamGb} GB minus ${input.machine.minFreeRamGb} GB reserve fits ${Math.max(0, ramBound - input.running)} more agent(s) at ${input.machine.agentRssMb} MB each`);
5002
+ if (ramBound < adaptive) reasons.push(`free RAM ${freeRamGb} GB minus ${input.machine.minFreeRamGb} GB reserve fits ${Math.max(0, ramBound - input.running)} more agent(s) at ${perAgentMb} MB each${measuredAgentBytes ? " (measured)" : ""}`);
4932
5003
  let maxAgents = Math.min(adaptive, ramBound);
4933
5004
  if (wsl && maxAgents > input.machine.wslCap) {
4934
5005
  maxAgents = input.machine.wslCap;
@@ -5069,7 +5140,12 @@ var selectModel = (config, role, availability, extraCandidates = []) => {
5069
5140
  var routeAllRoles = (config, availability, extrasByRole = {}) => Object.fromEntries(MODEL_ROLES.map((role) => [role, selectModel(config, role, availability, extrasByRole[role] ?? [])]));
5070
5141
  var rankModels = (config, role, availability, extraCandidates = []) => {
5071
5142
  const byId = new Map(availability.map((item) => [item.id, item]));
5072
- const { ranked } = availableFromTiers(config, role, availability);
5143
+ const { ranked, skipped } = availableFromTiers(config, role, availability);
5144
+ if (config.models.routing.pin[role]) {
5145
+ const pinned = applyPin(config, role, availability, skipped);
5146
+ if (pinned) return [pinned, ...ranked.filter((item) => !(item.provider === pinned.provider && item.model === pinned.model))];
5147
+ if (config.models.routing.pinStrict) return [];
5148
+ }
5073
5149
  const extras = [];
5074
5150
  let extraIndex = 1e4;
5075
5151
  for (const ref of extraCandidates) {
@@ -5190,6 +5266,32 @@ ${outcome.stderr}`);
5190
5266
  }
5191
5267
  return [];
5192
5268
  };
5269
+ var cliModelsCachePath = (stateDir, provider) => join(stateDir, "catalog", `cli-${provider}.json`);
5270
+ var readCliModelsCache = (stateDir, provider) => {
5271
+ const path = cliModelsCachePath(stateDir, provider);
5272
+ if (!existsSync(path)) return null;
5273
+ try {
5274
+ const raw = readJson2(path);
5275
+ return typeof raw.fetchedAt === "string" && Array.isArray(raw.ids) ? { fetchedAt: raw.fetchedAt, ids: raw.ids } : null;
5276
+ } catch {
5277
+ return null;
5278
+ }
5279
+ };
5280
+ var writeCliModelsCache = (stateDir, provider, ids, now4 = /* @__PURE__ */ new Date()) => {
5281
+ const path = cliModelsCachePath(stateDir, provider);
5282
+ mkdirSync(dirname(path), { recursive: true });
5283
+ const tmp = `${path}.${process.pid}.tmp`;
5284
+ writeFileSync(tmp, `${JSON.stringify({ fetchedAt: now4.toISOString(), ids }, null, 2)}
5285
+ `, "utf8");
5286
+ renameSync(tmp, path);
5287
+ };
5288
+ var listCliModelsCached = async (provider, bin, runner, stateDir, cacheHours, now4 = () => /* @__PURE__ */ new Date()) => {
5289
+ const cached = readCliModelsCache(stateDir, provider);
5290
+ if (cached && now4().getTime() - Date.parse(cached.fetchedAt) <= cacheHours * 36e5) return cached.ids;
5291
+ const ids = await listCliModels(provider, bin, runner);
5292
+ if (ids.length) writeCliModelsCache(stateDir, provider, ids, now4());
5293
+ return ids.length ? ids : cached?.ids ?? [];
5294
+ };
5193
5295
  var parseArtificialAnalysisPayload = (payload) => {
5194
5296
  const root = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
5195
5297
  const data = Array.isArray(root["data"]) ? root["data"] : Array.isArray(payload) ? payload : [];
@@ -5274,7 +5376,7 @@ var resolveCatalogCandidates = async (input) => {
5274
5376
  const settings = config.models.providers[provider];
5275
5377
  if (settings) {
5276
5378
  try {
5277
- const ids = await listCliModels(provider, settings.bin, input.runner);
5379
+ const ids = input.stateDir ? await listCliModelsCached(provider, settings.bin, input.runner, input.stateDir, config.models.catalog.cliCacheHours, input.now) : await listCliModels(provider, settings.bin, input.runner);
5278
5380
  for (const id2 of ids) {
5279
5381
  const resolved = resolveAlias(provider, id2, aliases);
5280
5382
  const existing = builtin[provider]?.models.find((model) => model.id === resolved);
@@ -5306,9 +5408,9 @@ var resolveCatalogCandidates = async (input) => {
5306
5408
  const matches2 = models.models.filter((model) => model.creatorSlug === creator || model.creatorSlug.includes(creator));
5307
5409
  for (const model of matches2) {
5308
5410
  const id2 = resolveAlias(provider, model.slug, aliases);
5309
- const score3 = model.codingIndex ?? model.intelligenceIndex ?? 50;
5310
- const quality = score3 >= 80 ? "frontier" : score3 >= 60 ? "balanced" : "fast";
5311
- push(provider, { id: id2, quality, codingScore: score3, source: "artificial-analysis", creator });
5411
+ const score4 = model.codingIndex ?? model.intelligenceIndex ?? 50;
5412
+ const quality = score4 >= 80 ? "frontier" : score4 >= 60 ? "balanced" : "fast";
5413
+ push(provider, { id: id2, quality, codingScore: score4, source: "artificial-analysis", creator });
5312
5414
  }
5313
5415
  }
5314
5416
  }
@@ -5366,6 +5468,16 @@ var clearProviderCooldown = (stateDir, provider) => {
5366
5468
  writeCooldowns(stateDir, rest);
5367
5469
  };
5368
5470
  var rotationStatePath = (stateDir) => join(stateDir, "queue-owner.json");
5471
+ var countRotationBlockingLeases = (loaded, leases) => leases.filter((lease) => {
5472
+ const path = join(loaded.stateDir, "issues", lease.issue, "delivery.json");
5473
+ if (!existsSync(path)) return true;
5474
+ try {
5475
+ const delivery = JSON.parse(readFileSync(path, "utf8"));
5476
+ return delivery.prNumber == null && !delivery.heldFor && !delivery.finalOutcome;
5477
+ } catch {
5478
+ return true;
5479
+ }
5480
+ }).length;
5369
5481
  var queueOwner = (loaded) => {
5370
5482
  const { rotation } = loaded.config.linear;
5371
5483
  if (!rotation.enabled || !rotation.owners.length) return loaded.config.linear.person;
@@ -5543,7 +5655,8 @@ var runLoopDoctor = async (input) => {
5543
5655
  push("orca.worktrees", "warning", `worktree ps unavailable: ${workersError}`);
5544
5656
  }
5545
5657
  const running = countRunningWorkers(worktrees);
5546
- const machine = assessSlots({ machine: config.machine, running, platform: input.platform });
5658
+ const orcaMemory = await orcaDiagnosticsMemory(input.runner, orcaOptions2);
5659
+ const machine = assessSlots({ machine: config.machine, running, platform: input.platform, orcaMemory });
5547
5660
  push("machine.slots", machine.free > 0 ? "passed" : "warning", `${machine.free} free of ${machine.maxAgents} (running ${running}, cpus ${machine.sample.cpus}, load ${machine.sample.load1PerCpuPercent}%, free RAM ${machine.freeRamGb} GB)${machine.reasons.length ? `; ${machine.reasons.join("; ")}` : ""}`);
5548
5661
  let queue = [];
5549
5662
  let queueError = null;
@@ -5860,10 +5973,10 @@ var planMemoryContext = async (input) => {
5860
5973
  hits = [];
5861
5974
  }
5862
5975
  const selected = selectMemoryForPrompt(hits, memory);
5863
- const beforeChars = input.references.reduce((sum, ref) => sum + JSON.stringify(ref).length, 0) + issueBudgetDefault;
5976
+ const beforeChars = input.references.reduce((sum2, ref) => sum2 + JSON.stringify(ref).length, 0) + issueBudgetDefault;
5864
5977
  const preferred = memory.preferOverDocBridge ? preferMemoryOverDocBridge(input.references, selected.hits, memory.minDocBridgeWhenMemory) : { references: input.references};
5865
5978
  const issueCharBudget = selected.hits.length && memory.shrinkIssueCharsWhenMemory ? Math.min(issueBudgetDefault, memory.issueCharsWithMemory) : issueBudgetDefault;
5866
- const afterChars = preferred.references.reduce((sum, ref) => sum + JSON.stringify(ref).length, 0) + issueCharBudget + selected.approxChars;
5979
+ const afterChars = preferred.references.reduce((sum2, ref) => sum2 + JSON.stringify(ref).length, 0) + issueCharBudget + selected.approxChars;
5867
5980
  return {
5868
5981
  hits: selected.hits,
5869
5982
  references: preferred.references,
@@ -6420,9 +6533,14 @@ var writeDispatchRecord = (stateDir, record3) => {
6420
6533
  writeJson2(path, record3);
6421
6534
  return path;
6422
6535
  };
6423
- var appendLoopEvent = (stateDir, event2, bus) => {
6536
+ var EVENTS_ROTATE_AT_BYTES = 10 * 1024 * 1024;
6537
+ var appendLoopEvent = (stateDir, event2, bus, now4 = () => /* @__PURE__ */ new Date()) => {
6424
6538
  const path = join(stateDir, "events.ndjson");
6425
6539
  mkdirSync(dirname(path), { recursive: true });
6540
+ try {
6541
+ if (statSync(path).size > EVENTS_ROTATE_AT_BYTES) renameSync(path, join(stateDir, `events-archive-${now4().getTime()}.ndjson`));
6542
+ } catch {
6543
+ }
6426
6544
  appendFileSync(path, `${JSON.stringify(event2)}
6427
6545
  `, "utf8");
6428
6546
  if (bus && typeof event2["type"] === "string") bus.emit(event2);
@@ -6431,11 +6549,12 @@ var gatherLoopState = async (input) => {
6431
6549
  const { config } = input.loaded;
6432
6550
  const person = queueOwner(input.loaded);
6433
6551
  const orca = { bin: config.orca.bin, timeoutMs: config.orca.timeoutMs };
6434
- const [accountList, agentHooks, worktrees, queue] = await Promise.all([
6552
+ const [accountList, agentHooks, worktrees, queue, orcaMemory] = await Promise.all([
6435
6553
  orcaAccountList(input.runner, orca).catch(() => ({})),
6436
6554
  orcaAgentHooks(input.runner, orca).catch(() => ({})),
6437
6555
  orcaWorktrees(input.runner, orca),
6438
- fetchLinearQueue(input.runner, { bin: config.orca.bin, workspaceId: config.linear.workspaceId, teamKey: config.linear.teamKey, assignee: person, filter: config.linear, orca })
6556
+ fetchLinearQueue(input.runner, { bin: config.orca.bin, workspaceId: config.linear.workspaceId, teamKey: config.linear.teamKey, assignee: person, filter: config.linear, orca }),
6557
+ orcaDiagnosticsMemory(input.runner, orca)
6439
6558
  ]);
6440
6559
  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 });
6441
6560
  const availableIds = providers.filter((provider) => provider.available).map((provider) => provider.id);
@@ -6450,11 +6569,11 @@ var gatherLoopState = async (input) => {
6450
6569
  })]))) : {};
6451
6570
  const routing = routeAllRoles(config, providers, extrasByRole);
6452
6571
  const running = countRunningWorkers(worktrees);
6453
- const slots = assessSlots({ machine: config.machine, running, platform: input.platform, ...input.machine });
6572
+ const slots = assessSlots({ machine: config.machine, running, platform: input.platform, orcaMemory, ...input.machine });
6454
6573
  const leases = input.ledger.active();
6455
6574
  const busy = busyIssues(queue, leases, worktrees, person);
6456
6575
  const candidates = queue.filter((issue) => !busy.has(issue.identifier) && (!input.onlyIssue || issue.identifier === input.onlyIssue));
6457
- return { person, providers, routing, worktrees, slots, queue, leases, busy, candidates };
6576
+ return { person, providers, routing, worktrees, slots, queue, leases, busy, candidates, extrasByRole };
6458
6577
  };
6459
6578
  var precheckTick = async (input) => {
6460
6579
  const loaded = input.loaded ?? loadLoopConfig(input.configPath);
@@ -6495,16 +6614,7 @@ var runTick = async (input) => {
6495
6614
  }
6496
6615
  const state = await gatherLoopState({ loaded, runner: input.runner, ledger, env: input.env, platform: input.platform, now: now4, onlyIssue: input.onlyIssue, machine: input.machine });
6497
6616
  const orchestrator = state.routing["orchestrator"] ?? { role: "orchestrator", selected: null, skipped: [] };
6498
- const orchestratorExtras = config.models.routing.mode === "catalog" ? await resolveCatalogCandidates({
6499
- config,
6500
- role: "orchestrator",
6501
- availableProviderIds: state.providers.filter((provider) => provider.available).map((provider) => provider.id),
6502
- runner: input.runner,
6503
- stateDir: loaded.stateDir,
6504
- env: input.env,
6505
- now: now4
6506
- }) : [];
6507
- const orchestratorCandidates = rankModels(config, "orchestrator", state.providers, orchestratorExtras);
6617
+ const orchestratorCandidates = rankModels(config, "orchestrator", state.providers, state.extrasByRole["orchestrator"] ?? []);
6508
6618
  const onProviderFailure = (failure) => {
6509
6619
  if (dryRun) return;
6510
6620
  const resetsAt = extractResetsAt(failure.detail, now4());
@@ -6524,7 +6634,7 @@ var runTick = async (input) => {
6524
6634
  return { ...base, status: "idle", results, notes };
6525
6635
  }
6526
6636
  if (!state.candidates.length) {
6527
- const rotation = dryRun ? { owner: state.person, advanced: false } : advanceQueueOwner(loaded, { queueEmpty: state.queue.length === 0, activeLeases: state.leases.length, now: now4() });
6637
+ const rotation = dryRun ? { owner: state.person, advanced: false } : advanceQueueOwner(loaded, { queueEmpty: state.queue.length === 0, activeLeases: countRotationBlockingLeases(loaded, state.leases), now: now4() });
6528
6638
  if (rotation.advanced) notes.push(`queue drained for ${state.person}; switched to ${rotation.owner}`);
6529
6639
  else notes.push("queue has no dispatchable candidate");
6530
6640
  return { ...base, status: "idle", results, notes };
@@ -6557,17 +6667,32 @@ The loop will not retry this issue until you remove the \`${config.resilience.pa
6557
6667
  appendLoopEvent(loaded.stateDir, { at: now4().toISOString(), type: "issue.paused", issue, kind, consecutive: failureState.consecutive, reason }, bus);
6558
6668
  await bus.runHook("onPause", { issue, kind, consecutive: failureState.consecutive, reason });
6559
6669
  };
6670
+ let pinnedSkillsOnce;
6671
+ const getPinnedSkills = () => {
6672
+ if (pinnedSkillsOnce === void 0) {
6673
+ try {
6674
+ pinnedSkillsOnce = loadPinnedSkills(loaded.root, config.brief.skills, config.brief.maxSkillChars);
6675
+ } catch (error) {
6676
+ pinnedSkillsOnce = { error };
6677
+ throw error;
6678
+ }
6679
+ }
6680
+ if ("error" in pinnedSkillsOnce) throw pinnedSkillsOnce.error;
6681
+ return pinnedSkillsOnce;
6682
+ };
6560
6683
  let dispatched = 0;
6561
6684
  for (const candidate of state.candidates) {
6562
6685
  if (dispatched >= budget) break;
6563
6686
  const setupBudgetMs = config.project.setup.command ? Number.isFinite(timeBudgetMs) ? Math.min(config.project.setup.timeoutSec * 1e3, Math.max(0, timeBudgetMs - config.contract.timeoutMs - 125e3)) : config.project.setup.timeoutSec * 1e3 : 0;
6564
- if (remainingMs() < config.contract.timeoutMs + setupBudgetMs + 12e4 && !readStoredContract(loaded.stateDir, candidate.identifier)) {
6687
+ const cachedContract = readStoredContract(loaded.stateDir, candidate.identifier);
6688
+ if (remainingMs() < config.contract.timeoutMs + setupBudgetMs + 12e4 && !cachedContract) {
6565
6689
  notes.push(`time budget: ${candidate.identifier} left for the next tick (${Math.round(remainingMs() / 1e3)}s remaining)`);
6566
6690
  continue;
6567
6691
  }
6568
- if (isIssuePaused(loaded.stateDir, candidate.identifier)) {
6692
+ const failureState = readIssueFailures(loaded.stateDir, candidate.identifier);
6693
+ if (failureState.pausedAt !== null) {
6569
6694
  if (candidate.labels.includes(config.resilience.pausedLabel)) {
6570
- results.push({ issue: candidate.identifier, outcome: "skipped", reason: `paused after ${readIssueFailures(loaded.stateDir, candidate.identifier).consecutive} consecutive failures; remove the "${config.resilience.pausedLabel}" label or run "ak-harness loop resume ${candidate.identifier}" to retry` });
6695
+ results.push({ issue: candidate.identifier, outcome: "skipped", reason: `paused after ${failureState.consecutive} consecutive failures; remove the "${config.resilience.pausedLabel}" label or run "ak-harness loop resume ${candidate.identifier}" to retry` });
6571
6696
  continue;
6572
6697
  }
6573
6698
  if (!dryRun) clearIssueFailures(loaded.stateDir, candidate.identifier);
@@ -6580,8 +6705,8 @@ The loop will not retry this issue until you remove the \`${config.resilience.pa
6580
6705
  results.push({ issue: candidate.identifier, outcome: "failed", reason: `issue fetch failed: ${message2(error)}` });
6581
6706
  continue;
6582
6707
  }
6583
- let stored = readStoredContract(loaded.stateDir, detail.identifier);
6584
- const memoryProbe = memory ? await planMemoryContext({
6708
+ let stored = cachedContract;
6709
+ const memoryPlan = memory ? await planMemoryContext({
6585
6710
  adapter: memory,
6586
6711
  config,
6587
6712
  issueId: detail.identifier,
@@ -6589,7 +6714,7 @@ The loop will not retry this issue until you remove the \`${config.resilience.pa
6589
6714
  project: config.project.name,
6590
6715
  references: []
6591
6716
  }) : null;
6592
- if (stored && !contractIsFresh(stored, detail, config.contract.reuseHours, now4(), memoryProbe?.memoryDigest)) stored = null;
6717
+ if (stored && !contractIsFresh(stored, detail, config.contract.reuseHours, now4(), memoryPlan?.memoryDigest)) stored = null;
6593
6718
  if (!stored) {
6594
6719
  if (input.skipContractGeneration) {
6595
6720
  results.push({ issue: detail.identifier, outcome: "skipped", reason: "no cached contract; generation skipped" });
@@ -6689,16 +6814,9 @@ The loop will not retry this issue until you remove the \`${config.resilience.pa
6689
6814
  }
6690
6815
  if (setupFailed) notes.push(`${detail.identifier}: setup command failed but project.setup.required is false \u2014 continuing`);
6691
6816
  }
6692
- const briefMemory = memory ? await planMemoryContext({
6693
- adapter: memory,
6694
- config,
6695
- issueId: detail.identifier,
6696
- issueTitle: detail.title,
6697
- project: config.project.name,
6698
- references: []
6699
- }) : { memoryBlock: "", issueCharBudget: config.contract.maxIssueChars, hits: [] };
6817
+ const briefMemory = memoryPlan ?? { memoryBlock: "", issueCharBudget: config.contract.maxIssueChars, hits: [] };
6700
6818
  const guidanceRefs = config.contract.maxBriefReferences > 0 && config.contract.briefScopes.length ? await resolveDocContext(loaded.root, `${detail.identifier} ${detail.title}`, config.contract.maxBriefReferences, config.contract.briefScopes) : [];
6701
- const pinnedSkills = loadPinnedSkills(loaded.root, config.brief.skills, config.brief.maxSkillChars);
6819
+ const pinnedSkills = getPinnedSkills();
6702
6820
  const brief = renderWorkerBrief({
6703
6821
  issue: detail,
6704
6822
  contract: stored,
@@ -6963,14 +7081,34 @@ ${JSON.stringify(stored.contract, null, 2)}
6963
7081
  return false;
6964
7082
  }
6965
7083
  };
7084
+ var captureWorkerOutput = async (ctx, terminal2) => {
7085
+ if (!terminal2) return null;
7086
+ try {
7087
+ const screen = (await orcaTerminalScreen(ctx.runner, { terminal: terminal2 }, orcaOptions(ctx.config))).trim();
7088
+ return screen ? screen.slice(-2e3) : null;
7089
+ } catch {
7090
+ return null;
7091
+ }
7092
+ };
6966
7093
  var escalateLinear = async (ctx, record3, kind, body3, actions) => {
6967
7094
  if (ctx.dryRun) {
6968
7095
  actions.push(`would mark ${kind} in Linear and Orca`);
6969
7096
  return;
6970
7097
  }
7098
+ const workerOutput = await captureWorkerOutput(ctx, record3.terminal);
7099
+ const fullBody = workerOutput ? `${body3}
7100
+
7101
+ <details><summary>Worker's last terminal output</summary>
7102
+
7103
+ \`\`\`
7104
+ ${workerOutput}
7105
+ \`\`\`
7106
+
7107
+ </details>` : body3;
7108
+ if (workerOutput) actions.push("captured worker terminal output for the escalation");
6971
7109
  const linear = linearOptions(ctx.config);
6972
7110
  try {
6973
- await linearCommentAdd(ctx.runner, { issue: record3.issue, body: `${body3}
7111
+ await linearCommentAdd(ctx.runner, { issue: record3.issue, body: `${fullBody}
6974
7112
 
6975
7113
  <!-- loop:${kind}:${record3.leaseId} -->`, dedupeKey: `${kind}:${record3.issue}:${record3.leaseId}` }, linear);
6976
7114
  await linearLabelAdd(ctx.runner, { issue: record3.issue, labels: [ctx.config.linear.blockedLabel] }, linear);
@@ -7028,7 +7166,7 @@ var providerUnavailable = (ctx, providerId) => {
7028
7166
  return !match || !match.available;
7029
7167
  };
7030
7168
  var pickHandoffBuilder = (ctx, record3) => {
7031
- const ranked = rankModels(ctx.config, "builder", ctx.providers);
7169
+ const ranked = rankModels(ctx.config, "builder", ctx.providers, ctx.builderExtras);
7032
7170
  const different = ranked.find((candidate) => candidate.provider !== record3.provider || candidate.model !== record3.model);
7033
7171
  return different ?? null;
7034
7172
  };
@@ -7469,7 +7607,8 @@ var runDeliver = async (input) => {
7469
7607
  const availableIds = providers.filter((provider) => provider.available).map((provider) => provider.id);
7470
7608
  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([]);
7471
7609
  const reviewer = rankModels(config, "reviewer", providers, await catalogExtras("reviewer"))[0] ?? null;
7472
- const builder = rankModels(config, "builder", providers, await catalogExtras("builder"))[0] ?? null;
7610
+ const builderExtras = await catalogExtras("builder");
7611
+ const builder = rankModels(config, "builder", providers, builderExtras)[0] ?? null;
7473
7612
  let env = input.env ?? process.env;
7474
7613
  if (!env["GITHUB_TOKEN"] && !env["GH_TOKEN"]) {
7475
7614
  try {
@@ -7485,13 +7624,14 @@ var runDeliver = async (input) => {
7485
7624
  const { errors } = await loadLoopPlugins(loaded.root, config.plugins.modules, bus);
7486
7625
  for (const failure of errors) notes.push(`plugin ${failure.path} failed to load: ${failure.error}`);
7487
7626
  }
7488
- const ctx = { loaded, config, runner: input.runner, now: now4, dryRun, reviewer, builder, providers, env, ...input.assumeIdle === void 0 ? {} : { assumeIdle: input.assumeIdle }, notes, reviewDeadlineMs, bus };
7627
+ const ctx = { loaded, config, runner: input.runner, now: now4, dryRun, reviewer, builder, providers, env, ...input.assumeIdle === void 0 ? {} : { assumeIdle: input.assumeIdle }, notes, reviewDeadlineMs, bus, builderExtras };
7489
7628
  const ledger = createDispatchLedger(loaded.stateDir);
7490
7629
  const leases = new Map(ledger.active().map((lease) => [lease.issue, lease]));
7491
7630
  const results = [];
7492
7631
  for (const record3 of listDispatched(loaded.stateDir)) {
7493
7632
  if (input.onlyIssue && record3.issue !== input.onlyIssue) continue;
7494
7633
  let state = readDeliveryState(loaded.stateDir, record3.issue);
7634
+ if (state.finishedAt && state.finalOutcome === "merged") continue;
7495
7635
  const lease = leases.get(record3.issue);
7496
7636
  if (!state.finishedAt) {
7497
7637
  const ageMinutes = minutesBetween(now4(), record3.dispatchedAt);
@@ -7530,7 +7670,6 @@ var runDeliver = async (input) => {
7530
7670
  results.push(await handlePullRequest(ctx, record3, lease, state, pr));
7531
7671
  continue;
7532
7672
  }
7533
- if (state.finishedAt && state.finalOutcome === "merged") continue;
7534
7673
  const recordedMerge = readMergedEvent(loaded.stateDir, record3.issue);
7535
7674
  if (recordedMerge) {
7536
7675
  try {
@@ -8091,10 +8230,19 @@ ${step && total ? `${step}/${total} ` : ""}${title}`),
8091
8230
  ] }))
8092
8231
  };
8093
8232
  };
8233
+ var newestIssueMtimeMs = (stateDir, issue) => {
8234
+ const mtimes = [dispatchRecordPath(stateDir, issue), deliveryStatePath(stateDir, issue), contractPath(stateDir, issue)].map((path) => {
8235
+ try {
8236
+ return statSync(path).mtimeMs;
8237
+ } catch {
8238
+ return null;
8239
+ }
8240
+ }).filter((value) => value !== null);
8241
+ return mtimes.length ? Math.max(...mtimes) : null;
8242
+ };
8094
8243
  var HARNESS_REPO_URL = "https://github.com/AgentsKit-io/harness";
8095
8244
  var isRecord14 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
8096
- var readLoopEvents = (stateDir) => {
8097
- const path = join(stateDir, "events.ndjson");
8245
+ var parseEventsFile = (path) => {
8098
8246
  if (!existsSync(path)) return [];
8099
8247
  return readFileSync(path, "utf8").split(/\r?\n/).filter(Boolean).flatMap((line2) => {
8100
8248
  try {
@@ -8105,6 +8253,11 @@ var readLoopEvents = (stateDir) => {
8105
8253
  }
8106
8254
  });
8107
8255
  };
8256
+ var eventsArchivePattern = /^events-archive-(\d+)\.ndjson$/;
8257
+ var readLoopEvents = (stateDir, sinceMs) => {
8258
+ const archives = existsSync(stateDir) ? readdirSync(stateDir).map((name2) => name2.match(eventsArchivePattern)).filter((match) => match !== null).map((match) => ({ path: join(stateDir, match[0]), rotatedAtMs: Number(match[1]) })).filter((archive) => sinceMs === void 0 || archive.rotatedAtMs >= sinceMs).sort((a, b) => a.rotatedAtMs - b.rotatedAtMs) : [];
8259
+ return [...archives.flatMap((archive) => parseEventsFile(archive.path)), ...parseEventsFile(join(stateDir, "events.ndjson"))];
8260
+ };
8108
8261
  var parseSince = (value, now4) => {
8109
8262
  if (!value) return new Date(now4.getTime() - 7 * 864e5);
8110
8263
  const match = value.match(/^(\d+)([dhm])$/);
@@ -8151,10 +8304,11 @@ var buildSuggestions = (input) => {
8151
8304
  var buildRetroReport = async (input) => {
8152
8305
  const loaded = input.loaded ?? loadLoopConfig(input.configPath);
8153
8306
  const { config } = loaded;
8307
+ const person = queueOwner(loaded);
8154
8308
  const now4 = (input.now ?? (() => /* @__PURE__ */ new Date()))();
8155
8309
  const since = parseSince(input.since, now4);
8156
8310
  const inWindow = (at) => typeof at === "string" && Date.parse(at) >= since.getTime() && Date.parse(at) <= now4.getTime();
8157
- const events = readLoopEvents(loaded.stateDir).filter((event2) => inWindow(event2.at));
8311
+ const events = readLoopEvents(loaded.stateDir, since.getTime()).filter((event2) => inWindow(event2.at));
8158
8312
  const counts = {};
8159
8313
  for (const event2 of events) counts[event2.type] = (counts[event2.type] ?? 0) + 1;
8160
8314
  const escalations = events.filter((event2) => event2.type === "contract.escalated");
@@ -8176,6 +8330,8 @@ var buildRetroReport = async (input) => {
8176
8330
  if (existsSync(issuesDir)) for (const entry of readdirSync(issuesDir, { withFileTypes: true })) {
8177
8331
  if (!entry.isDirectory()) continue;
8178
8332
  const issue = entry.name;
8333
+ const newestMtime = newestIssueMtimeMs(loaded.stateDir, issue);
8334
+ if (newestMtime !== null && newestMtime < since.getTime()) continue;
8179
8335
  const dispatch = readDispatchRecord(loaded.stateDir, issue);
8180
8336
  const delivery = readDeliveryState(loaded.stateDir, issue);
8181
8337
  const contract = readStoredContract(loaded.stateDir, issue);
@@ -8233,7 +8389,7 @@ var buildRetroReport = async (input) => {
8233
8389
  else if (status === "ok") work += 1;
8234
8390
  }
8235
8391
  }
8236
- orca = { runs, idle, work, timedOut, avgDurationSec: durations.length ? Math.round(durations.reduce((sum, value) => sum + value, 0) / durations.length) : null, maxDurationSec: durations.length ? Math.round(Math.max(...durations)) : null };
8392
+ orca = { runs, idle, work, timedOut, avgDurationSec: durations.length ? Math.round(durations.reduce((sum2, value) => sum2 + value, 0) / durations.length) : null, maxDurationSec: durations.length ? Math.round(Math.max(...durations)) : null };
8237
8393
  } catch {
8238
8394
  orca = null;
8239
8395
  }
@@ -8242,9 +8398,9 @@ var buildRetroReport = async (input) => {
8242
8398
  generatedAt: now4.toISOString(),
8243
8399
  window: { since: since.toISOString(), until: now4.toISOString(), days: Number(((now4.getTime() - since.getTime()) / 864e5).toFixed(2)) },
8244
8400
  project: config.project.repo,
8245
- person: config.linear.person,
8401
+ person,
8246
8402
  counts,
8247
- escalations: { total: escalations.length, issues: [...new Set(escalations.map((event2) => String(event2.issue ?? "?")))], reasons: [...reasonCounts.entries()].map(([reason2, count2]) => ({ reason: reason2, count: count2 })).sort((left, right) => right.count - left.count) },
8403
+ escalations: { total: escalations.length, issues: [...new Set(escalations.map((event2) => String(event2.issue ?? "?")))], reasons: [...reasonCounts.entries()].map(([reason2, count3]) => ({ reason: reason2, count: count3 })).sort((left, right) => right.count - left.count) },
8248
8404
  dispatches: { total: dispatchEvents.length, failed: counts["worker.dispatch-failed"] ?? 0, byProvider },
8249
8405
  delivery: { merged: tally("merged"), blocked: tally("blocked"), stuck: tally("stuck"), abandoned: tally("abandoned"), inFlight: tally("in-flight"), fixRounds, reviewsClean, reviewsFindings, reviewsIncomplete, medianLeadTimeMin: median3(rows.map((row) => row.leadTimeMin).filter((value) => value !== null)) },
8250
8406
  providers: { cooldowns, cooldownEvents: counts["provider.cooldown"] ?? 0 },
@@ -8271,7 +8427,7 @@ var renderRetroMarkdown = (report) => {
8271
8427
  if (report.orca) lines.push(`| Orca runs (idle / work / timed out) | ${report.orca.runs} (${report.orca.idle} / ${report.orca.work} / ${report.orca.timedOut}) \xB7 avg ${report.orca.avgDurationSec ?? "\u2014"} s \xB7 max ${report.orca.maxDurationSec ?? "\u2014"} s |`);
8272
8428
  lines.push("");
8273
8429
  if (Object.keys(report.dispatches.byProvider).length) {
8274
- lines.push("## Providers", "", ...Object.entries(report.dispatches.byProvider).map(([key, count2]) => `- ${key}: ${count2} dispatch(es)`), ...report.providers.cooldowns.map((row) => `- cooldown ${row.provider} until ${row.until.slice(0, 16)}Z \u2014 ${row.reason}`), "");
8430
+ lines.push("## Providers", "", ...Object.entries(report.dispatches.byProvider).map(([key, count3]) => `- ${key}: ${count3} dispatch(es)`), ...report.providers.cooldowns.map((row) => `- cooldown ${row.provider} until ${row.until.slice(0, 16)}Z \u2014 ${row.reason}`), "");
8275
8431
  }
8276
8432
  if (report.escalations.reasons.length) {
8277
8433
  lines.push("## Problems", "", ...report.escalations.reasons.map((row) => `- ${row.count}\xD7 ${row.reason}`), ...report.issues.filter((row) => ["blocked", "stuck", "abandoned"].includes(row.outcome)).map((row) => `- ${row.issue} ${row.outcome}${row.pr ? ` (PR #${row.pr})` : ""} after ${row.fixRounds} fix round(s), ${row.nudges} nudge(s)`), "");
@@ -8452,7 +8608,7 @@ var buildDebriefReport = (input) => {
8452
8608
  }
8453
8609
  const inFlight = rows.filter((row) => !row.finalOutcome && row.phase !== "escalated");
8454
8610
  const held = rows.filter((row) => row.phase === "held" || row.phase === "held-incomplete-review" || row.heldFor);
8455
- const events = readLoopEvents(stateDir).filter((event2) => Date.parse(event2.at) >= since.getTime());
8611
+ const events = readLoopEvents(stateDir, since.getTime()).filter((event2) => Date.parse(event2.at) >= since.getTime());
8456
8612
  const recentEscalations = events.filter((event2) => event2.type === "contract.escalated").slice(-10).map((event2) => ({
8457
8613
  issue: typeof event2.issue === "string" ? event2.issue : "?",
8458
8614
  at: event2.at,
@@ -8534,6 +8690,138 @@ var renderDebriefMarkdown = (report) => {
8534
8690
  lines.push("_Read-only. Run `ak-harness loop deliver` / `tick` to act; `loop retro` for the weekly digest._");
8535
8691
  return lines.join("\n");
8536
8692
  };
8693
+ var connectedStatuses = /* @__PURE__ */ new Set(["connected", "running", "active", "idle"]);
8694
+ var stalledPhases = /* @__PURE__ */ new Set(["waiting-for-pr", "awaiting-review", "review-incomplete", "fix-round"]);
8695
+ var heldRow = (row) => Boolean(row.heldFor) || row.phase === "held" || row.phase === "held-incomplete-review";
8696
+ var count2 = (events, type) => events.filter((event2) => event2.type === type).length;
8697
+ var sum = (events, key) => events.reduce((total, event2) => total + (typeof event2[key] === "number" && Number.isFinite(event2[key]) ? Number(event2[key]) : 0), 0);
8698
+ var uniqueIssues = (events, types) => new Set(events.filter((event2) => types.includes(event2.type) && typeof event2.issue === "string").map((event2) => event2.issue)).size;
8699
+ var assessObservability = (input) => {
8700
+ const anomalies = [];
8701
+ for (const issue of input.missingDeliveryIssues) anomalies.push({ id: "claim-without-delivery", severity: "action_required", issue, message: `${issue} has an active claim but no delivery.json`, evidence: { issue } });
8702
+ for (const terminal2 of input.terminals) {
8703
+ if (terminal2.worktreeId && connectedStatuses.has(terminal2.status.toLowerCase()) && terminal2.lastOutputAt === null && !terminal2.preview.trim()) anomalies.push({ id: "connected-without-output", severity: "warning", issue: null, message: `terminal ${terminal2.handle} is connected but has not emitted output`, evidence: { handle: terminal2.handle, worktreeId: terminal2.worktreeId, status: terminal2.status } });
8704
+ }
8705
+ for (const worktree of input.finalizedDirtyWorktrees) anomalies.push({ id: "finalized-dirty-worktree", severity: "action_required", issue: worktree.issue, message: `finalized worktree ${worktree.worktreeId} still has ${worktree.files} uncommitted file(s)`, evidence: { ...worktree } });
8706
+ const latestDispatch = input.events.filter((event2) => event2.type === "worker.dispatched").map((event2) => Date.parse(event2.at)).filter(Number.isFinite).sort((a, b) => b - a)[0];
8707
+ const quietForMin = latestDispatch === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, Math.round((Date.parse(input.generatedAt) - latestDispatch) / 6e4));
8708
+ if (input.queueReady > 0 && input.freeSlots > 0 && quietForMin >= 15) anomalies.push({ id: "queue-ready-no-dispatch", severity: "action_required", issue: null, message: `${input.queueReady} ready issue(s) and ${input.freeSlots} free slot(s), but no dispatch in ${Number.isFinite(quietForMin) ? `${quietForMin} min` : "the observation window"}`, evidence: { queueReady: input.queueReady, freeSlots: input.freeSlots, quietForMin } });
8709
+ for (const row of input.issues) {
8710
+ if (row.heldFor || !stalledPhases.has(row.phase) || row.ageMin === null || row.ageMin < input.workerIdleTimeoutMin) continue;
8711
+ anomalies.push({ id: "stalled-delivery", severity: "action_required", issue: row.issue, message: `${row.issue} is in ${row.phase} for ${row.ageMin} min (threshold ${input.workerIdleTimeoutMin} min)`, evidence: { issue: row.issue, phase: row.phase, ageMin: row.ageMin, thresholdMin: input.workerIdleTimeoutMin } });
8712
+ }
8713
+ const events = {};
8714
+ for (const event2 of input.events) events[event2.type] = (events[event2.type] ?? 0) + 1;
8715
+ const report = {
8716
+ status: anomalies.some((item) => item.severity === "action_required") ? "action_required" : "healthy",
8717
+ generatedAt: input.generatedAt,
8718
+ project: input.project,
8719
+ person: input.person,
8720
+ windowHours: input.windowHours,
8721
+ anomalies,
8722
+ metrics: {
8723
+ queueReady: input.queueReady,
8724
+ freeSlots: input.freeSlots,
8725
+ runningWorkers: input.runningWorkers,
8726
+ maxAgents: input.maxAgents,
8727
+ activeClaims: input.activeClaims,
8728
+ inFlight: input.issues.filter((row) => !heldRow(row)).length,
8729
+ held: input.issues.filter(heldRow).length,
8730
+ merged: input.merged,
8731
+ blocked: input.blocked,
8732
+ fixRounds: input.fixRounds,
8733
+ reviewFindings: input.reviewFindings,
8734
+ reviewIncomplete: input.reviewIncomplete,
8735
+ medianLeadTimeMin: input.medianLeadTimeMin,
8736
+ providerRemainingPercent: input.providerRemainingPercent,
8737
+ machine: input.machine,
8738
+ memory: input.memory,
8739
+ cache: input.cache,
8740
+ tokens: input.tokens,
8741
+ events
8742
+ }
8743
+ };
8744
+ return report;
8745
+ };
8746
+ var compactTerminal = (terminal2) => ({ handle: terminal2.handle, status: terminal2.status, worktreeId: terminal2.worktreeId, lastOutputAt: terminal2.lastOutputAt, preview: terminal2.preview });
8747
+ var dirtyFinalizedWorktrees = async (runner, worktrees) => {
8748
+ const out = [];
8749
+ for (const worktree of worktrees) {
8750
+ if (worktree.workspaceStatus.trim().toLowerCase() !== "completed" || !worktree.path) continue;
8751
+ try {
8752
+ const result = await runner.run(["git", "-C", worktree.path, "status", "--porcelain"], { timeoutMs: 1e4 });
8753
+ if (result.code === 0 && result.stdout.trim()) out.push({ worktreeId: worktree.id, issue: worktree.linkedLinearIssue, files: result.stdout.trim().split(/\r?\n/).length });
8754
+ } catch {
8755
+ }
8756
+ }
8757
+ return out;
8758
+ };
8759
+ var runObservability = async (input) => {
8760
+ const loaded = input.loaded ?? loadLoopConfig(input.configPath ?? "loop.config.yaml");
8761
+ const now4 = input.now ?? (() => /* @__PURE__ */ new Date());
8762
+ const at = now4();
8763
+ const since = parseSince(input.since ?? "24h", at);
8764
+ const [doctor, debrief, worktrees, terminals] = await Promise.all([
8765
+ runLoopDoctor({ loaded, runner: input.runner, env: input.env, platform: input.platform, now: () => at, probe: false }),
8766
+ Promise.resolve(buildDebriefReport({ loaded, since: input.since ?? "24h", now: () => at })),
8767
+ orcaWorktrees(input.runner, { bin: loaded.config.orca.bin, timeoutMs: loaded.config.orca.timeoutMs }).catch(() => []),
8768
+ orcaTerminalList(input.runner, {}, { bin: loaded.config.orca.bin, timeoutMs: loaded.config.orca.timeoutMs }).catch(() => [])
8769
+ ]);
8770
+ const events = readLoopEvents(loaded.stateDir).filter((event2) => Date.parse(event2.at) >= since.getTime() && Date.parse(event2.at) <= at.getTime());
8771
+ const ledger = createDispatchLedger(loaded.stateDir);
8772
+ const active = ledger.active();
8773
+ const missingDeliveryIssues = active.filter((lease) => !existsSync(deliveryStatePath(loaded.stateDir, lease.issue))).map((lease) => lease.issue);
8774
+ const records = listDispatched(loaded.stateDir);
8775
+ const completed = records.map((record3) => ({ record: record3, state: readDeliveryState(loaded.stateDir, record3.issue) })).filter(({ state }) => state.finishedAt && Date.parse(state.finishedAt) >= since.getTime());
8776
+ const leadTimes = completed.map(({ record: record3, state }) => state.finishedAt ? (Date.parse(state.finishedAt) - Date.parse(record3.dispatchedAt)) / 6e4 : null).filter((value) => value !== null && Number.isFinite(value)).sort((a, b) => a - b);
8777
+ const medianLeadTimeMin = leadTimes.length ? leadTimes.length % 2 ? leadTimes[Math.floor(leadTimes.length / 2)] : (leadTimes[leadTimes.length / 2 - 1] + leadTimes[leadTimes.length / 2]) / 2 : null;
8778
+ const providerRemainingPercent = Object.fromEntries(doctor.providers.map((provider) => [provider.id, remainingUsagePercent(provider.usage, loaded.config.models.routing.usageMetric)]));
8779
+ const cachedContracts = records.filter((record3) => existsSync(contractPath(loaded.stateDir, record3.issue))).length;
8780
+ const memoryEvents = events.filter((event2) => event2.type === "memory.recalled");
8781
+ const tokens = { input: sum(events, "inputTokens"), output: sum(events, "outputTokens"), total: sum(events, "totalTokens"), cacheRead: sum(events, "cacheReadTokens"), cacheWrite: sum(events, "cacheWriteTokens") };
8782
+ const machine = { cpuCount: doctor.machine.sample.cpus, load1PerCpuPercent: doctor.machine.sample.load1PerCpuPercent, memoryUsedPercent: doctor.machine.sample.memoryUsedPercent, freeRamGb: doctor.machine.freeRamGb };
8783
+ const snapshot = {
8784
+ generatedAt: at.toISOString(),
8785
+ project: doctor.config.project,
8786
+ person: doctor.config.person,
8787
+ windowHours: Math.max(1, Math.round((at.getTime() - since.getTime()) / 36e5)),
8788
+ workerIdleTimeoutMin: loaded.config.delivery.workerIdleTimeoutMin,
8789
+ queueReady: doctor.queue.count,
8790
+ freeSlots: doctor.machine.free,
8791
+ runningWorkers: doctor.workers.running,
8792
+ maxAgents: doctor.machine.maxAgents,
8793
+ activeClaims: active.length,
8794
+ missingDeliveryIssues,
8795
+ terminals: terminals.map(compactTerminal),
8796
+ finalizedDirtyWorktrees: await dirtyFinalizedWorktrees(input.runner, worktrees),
8797
+ issues: debrief.inFlight.map(({ issue, phase: phase2, ageMin, heldFor }) => ({ issue, phase: phase2, ageMin, heldFor })),
8798
+ events,
8799
+ merged: uniqueIssues(events, ["pr.merged", "worker.merged"]),
8800
+ blocked: Math.max(records.filter((record3) => readDeliveryState(loaded.stateDir, record3.issue).finalOutcome === "blocked").length, uniqueIssues(events, ["worker.blocked"])),
8801
+ fixRounds: records.reduce((total, record3) => total + readDeliveryState(loaded.stateDir, record3.issue).fixRounds, 0),
8802
+ reviewFindings: count2(events, "pr.reviewed") - count2(events.filter((event2) => event2["status"] !== "findings"), "pr.reviewed"),
8803
+ reviewIncomplete: events.filter((event2) => event2.type === "pr.reviewed" && event2["status"] === "incomplete").length,
8804
+ medianLeadTimeMin,
8805
+ providerRemainingPercent,
8806
+ machine,
8807
+ memory: { recalls: memoryEvents.length, hits: sum(memoryEvents, "hits"), approxCharsSaved: sum(memoryEvents, "approxCharsSaved") },
8808
+ cache: { cachedContracts },
8809
+ tokens
8810
+ };
8811
+ return assessObservability(snapshot);
8812
+ };
8813
+ var renderObservabilityMarkdown = (report) => {
8814
+ const m = report.metrics;
8815
+ const headroom = Object.entries(m.providerRemainingPercent).map(([provider, remaining]) => `${provider} ${remaining === null ? "?" : `${remaining}%`}`).join(", ");
8816
+ const lines = [`# Loop observability \u2014 ${report.project} \xB7 ${report.person}`, "", `_${report.status}_ \xB7 generated ${report.generatedAt.slice(0, 19)}Z \xB7 last ${report.windowHours}h`, "", "## Metrics", "", `- Queue: ${m.queueReady} ready \xB7 ${m.freeSlots} free slot(s) \xB7 ${m.runningWorkers}/${m.maxAgents} workers`, `- Delivery: ${m.inFlight} in flight \xB7 ${m.held} held \xB7 ${m.merged} merged \xB7 ${m.blocked} blocked \xB7 ${m.fixRounds} fix round(s)`, `- Reviews: ${m.reviewFindings} findings \xB7 ${m.reviewIncomplete} incomplete`, `- Machine: ${m.machine.cpuCount} CPU \xB7 ${m.machine.load1PerCpuPercent}% load \xB7 ${m.machine.memoryUsedPercent}% memory \xB7 ${m.machine.freeRamGb} GB free`, `- Providers: ${headroom || "n/a"}`, `- Memory/cache: ${m.memory.recalls} recall(s), ${m.memory.hits} hit(s), ${m.memory.approxCharsSaved} chars saved \xB7 ${m.cache.cachedContracts} cached contract(s)`, `- Tokens observed: ${m.tokens.total || m.tokens.input + m.tokens.output || "n/a"}`, ""];
8817
+ if (report.anomalies.length) {
8818
+ lines.push("## Anomalies", "");
8819
+ for (const anomaly of report.anomalies) lines.push(`- **${anomaly.severity}**${anomaly.issue ? ` \xB7 ${anomaly.issue}` : ""}: ${anomaly.message}`);
8820
+ lines.push("");
8821
+ } else lines.push("## Anomalies", "", "_None detected._", "");
8822
+ lines.push("_Read-only. Run `ak-harness loop tick` or `deliver` to act on the queue._");
8823
+ return lines.join("\n");
8824
+ };
8537
8825
 
8538
8826
  // src/loop/watch.ts
8539
8827
  var defaultSleep = (ms) => new Promise((resolve10) => setTimeout(resolve10, ms));
@@ -8661,6 +8949,6 @@ var watchDeliveries = async (input) => {
8661
8949
  };
8662
8950
  var formatWatchEvent = (event2) => `${event2.kind}: ${event2.issue} \xB7 ${event2.message}`;
8663
8951
 
8664
- 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, advanceQueueOwner, 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, briefPath, buildDebriefReport, buildListIssuesArgv, buildRetroReport, buildReviewArgv, buildSuggestions, busyIssues, cancelRun, classifyFailure, classifyHarnessError, classifyProviderFailure, classifyWatchEvent, classifyWatchPhase, cleanTaskArtifacts, clearIssueFailures, 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, createLoopEventBus, createMachineMonitor, createMcpToolBridge, createModelPolicy, createOrcaDispatchPlan, createOrcaLifecycleProjection, createPhaseArtifact, createPhaseProfile, createPluginRegistry, createPluginSlot, createPolicyGate, createProcessRunner, createProcessToolRuntime, createPullRequestApproval, createQualityMatrix, createRagContextProvider, createRichIO, createSessionRecorder, createStatusSnapshot, createToolRuntime, createTrackingAdapter, createTrackingTransition, deliveryStatePath, detectProviders, discoverIntake, dispatchRecordPath, evaluateWatchdog, executePhaseProfile, exportEvidenceBundle, extractResetsAt, fetchArtificialAnalysisModels, fetchLinearIssue, fetchLinearQueue, fetchTeamMembers, filterAndOrderQueue, findExecutable, formatWatchEvent, gatherLoopState, generateContract, githubComment, githubCommentArgv, githubCommentExists, githubLabelRemove, githubMerge, githubMergeArgv, githubOpenPullRequests, githubPullRequest, githubPullRequestsForBranch, hasLocalConfig, hashContextSnapshot, hashContextSnapshots, hashMcpArgs, inspectDocBridgeIndex, inspectEventLogLock, installLoopAutomations, installPreflight, intakeIssueId, intakePath, isDiscoveryCurrent, isIssuePaused, isStagePaused, isWsl, issueFailurePath, launchWorkerTerminal, learningToMemoryRecord, learningsPath, linearAttach, linearAttachArgv, linearCommentAdd, linearCommentAddArgv, linearLabelAdd, linearLabelArgv, linearLabelRemove, linearStatusSet, linearStatusSetArgv, listCliModels, listDispatched, listIntake, listPausedIssues, loadAgentRegistry, loadAliases, loadBenchmarkManifest, loadBuiltinCatalog, loadConfig, loadLatestRun, loadLoopConfig, loadLoopPlugins, loadPinnedSkills, 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, pauseIssue, planFilePreflight, planMemoryContext, planPhaseProfile, planRun, precheckCommand, precheckDeliver, precheckTick, preferMemoryOverDocBridge, promoteLearnings, promoteLearningsToMemory, promptLocalConfig, providerIdentity, providerSpecs, queueOwner, rankModels, readAaCache, readArtifactFile, readContextSnapshots, readCooldowns, readDeliveryState, readDispatchRecord, readEvidenceTrustStore, readIntake, readIssueFailures, readLearningsLedger, readLoopEvents, readOutcomeProgress, readStagePause, readStoredContract, reconcileRun, recordBenchmarkObservation, recordIssueFailure, recordStageRunResult, recoverEventLogLock, recoveryDelayMs, remainingUsagePercent, renderArtifactMarkdown, renderContractPrompt, renderDebriefMarkdown, renderFindingsForWorker, renderHandoffBrief, renderHeadlessArgv, renderLocalConfig, renderPinnedSkills, renderRetroMarkdown, renderTuiCommand, renderWorkerBrief, resolveAgentForRole, resolveAlias, resolveCatalogCandidates, resolveDocContext, resumeIssue, resumeStage, resumeStateFromArtifacts, retroLearnings, retryRun, rotationStatePath, routeAllRoles, runAdversarialReview, runAgentEval, runCodeReview, runDeliver, runEvalBattery, runGuidedInstall, runLoopDoctor, runRetroStage, runTick, runWithRecovery, runWorkflow, sampleMachine, scanForPii, selectMemoryForPrompt, selectModel, selectRuntime, severityRank, shellQuote, skillDigest, skillRefs, snapshotWatchTargets, stageEntry, stagePausePath, 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 };
8952
+ 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, advanceQueueOwner, appendLoopEvent, approveRun, approvedDecision, artifactDigest, artifactFilePath, artifactIsFresh, artifactMarkdownPath, assertHuman, assessAcceptance, assessAgentEval, assessBlock, assessChecks, assessCompatibility, assessContract, assessDiscovery, assessImprovementCycle, assessIntegration, assessObservability, assessPilot, assessPreflight, assessProduction, assessQaTransition, assessSlots, assessWip, assessWorktreeCleanup, atLeast, authStatusFor, authorizeRun, automationName, automationPrompt, automationSpecs, availableMemoryBytes, benchmarkRuns, branchFor, briefPath, buildDebriefReport, buildListIssuesArgv, buildRetroReport, buildReviewArgv, buildSuggestions, busyIssues, cancelRun, classifyFailure, classifyHarnessError, classifyProviderFailure, classifyWatchEvent, classifyWatchPhase, cleanTaskArtifacts, clearIssueFailures, clearProviderCooldown, compareOptimization, compareVersions, composePullRequest, contractIsFresh, contractPath, cooldownPath, cooldownUntil, countRotationBlockingLeases, countRunningWorkers, createArgvRagContextProvider, createArtifactEnvelope, createCapabilityManifest, createCodingAgentAdapter, createCompatibilityManifest, createConfiguredToolRuntime, createDispatchLedger, createDocBridgeContextProvider, createDockerToolRuntime, createEvalManifest, createFileMemoryAdapter, createFileMemoryKvStore, createHarnessEventEnvelope, createInMemoryMemoryAdapter, createKvMemoryAdapter, createLinearTrackingAdapter, createLlmCache, createLlmCacheKey, createLoopEventBus, createMachineMonitor, createMcpToolBridge, createModelPolicy, createOrcaDispatchPlan, createOrcaLifecycleProjection, createPhaseArtifact, createPhaseProfile, createPluginRegistry, createPluginSlot, createPolicyGate, createProcessRunner, createProcessToolRuntime, createPullRequestApproval, createQualityMatrix, createRagContextProvider, createRichIO, createSessionRecorder, createStatusSnapshot, createToolRuntime, createTrackingAdapter, createTrackingTransition, deliveryStatePath, detectProviders, discoverIntake, dispatchRecordPath, evaluateWatchdog, executePhaseProfile, exportEvidenceBundle, extractResetsAt, fetchArtificialAnalysisModels, fetchLinearIssue, fetchLinearQueue, fetchTeamMembers, filterAndOrderQueue, findExecutable, formatWatchEvent, gatherLoopState, generateContract, githubComment, githubCommentArgv, githubCommentExists, githubLabelRemove, githubMerge, githubMergeArgv, githubOpenPullRequests, githubPullRequest, githubPullRequestsForBranch, hasLocalConfig, hashContextSnapshot, hashContextSnapshots, hashMcpArgs, inspectDocBridgeIndex, inspectEventLogLock, installLoopAutomations, installPreflight, intakeIssueId, intakePath, isDiscoveryCurrent, isIssuePaused, isStagePaused, isWsl, issueFailurePath, launchWorkerTerminal, learningToMemoryRecord, learningsPath, linearAttach, linearAttachArgv, linearCommentAdd, linearCommentAddArgv, linearLabelAdd, linearLabelArgv, linearLabelRemove, linearStatusSet, linearStatusSetArgv, listCliModels, listCliModelsCached, listDispatched, listIntake, listPausedIssues, loadAgentRegistry, loadAliases, loadBenchmarkManifest, loadBuiltinCatalog, loadConfig, loadLatestRun, loadLoopConfig, loadLoopPlugins, loadPinnedSkills, localConfigPath, loopStatus, markProviderExhausted, memoryDigestOf, mergeLoopConfig, modelFor, normalizeReason, openLoopMemory, orcaAccountList, orcaAgentHooks, orcaAutomationCreateArgv, orcaAutomationEditArgv, orcaAutomationRemove, orcaAutomationRun, orcaAutomationRuns, orcaAutomationsList, orcaDiagnosticsMemory, 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, pauseIssue, planFilePreflight, planMemoryContext, planPhaseProfile, planRun, precheckCommand, precheckDeliver, precheckTick, preferMemoryOverDocBridge, promoteLearnings, promoteLearningsToMemory, promptLocalConfig, providerIdentity, providerSpecs, queueOwner, rankModels, readAaCache, readArtifactFile, readCliModelsCache, readContextSnapshots, readCooldowns, readDeliveryState, readDispatchRecord, readEvidenceTrustStore, readIntake, readIssueFailures, readLearningsLedger, readLoopEvents, readOutcomeProgress, readStagePause, readStoredContract, reconcileRun, recordBenchmarkObservation, recordIssueFailure, recordStageRunResult, recoverEventLogLock, recoveryDelayMs, remainingUsagePercent, renderArtifactMarkdown, renderContractPrompt, renderDebriefMarkdown, renderFindingsForWorker, renderHandoffBrief, renderHeadlessArgv, renderLocalConfig, renderObservabilityMarkdown, renderPinnedSkills, renderRetroMarkdown, renderTuiCommand, renderWorkerBrief, resolveAgentForRole, resolveAlias, resolveCatalogCandidates, resolveDocContext, resumeIssue, resumeStage, resumeStateFromArtifacts, retroLearnings, retryRun, rotationStatePath, routeAllRoles, runAdversarialReview, runAgentEval, runCodeReview, runDeliver, runEvalBattery, runGuidedInstall, runLoopDoctor, runObservability, runRetroStage, runTick, runWithRecovery, runWorkflow, sampleMachine, scanForPii, selectMemoryForPrompt, selectModel, selectRuntime, severityRank, shellQuote, skillDigest, skillRefs, snapshotWatchTargets, stageEntry, stagePausePath, 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, writeCliModelsCache, writeDispatchRecord, writeIdFor, writeLearningsLedger, writeLocalConfig, writeStoredContract };
8665
8953
  //# sourceMappingURL=index.js.map
8666
8954
  //# sourceMappingURL=index.js.map