@agentskit/harness 0.11.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/cli.js CHANGED
@@ -2503,6 +2503,21 @@ var orcaStatus = async (runner, options2 = {}) => parseOrcaStatus(await orcaJson
2503
2503
  var orcaWorktrees = async (runner, options2 = {}) => parseOrcaWorktrees(await orcaJson(runner, ["worktree", "ps"], options2));
2504
2504
  var orcaAgentHooks = async (runner, options2 = {}) => parseOrcaAgentHooks(await orcaJson(runner, ["agent", "hooks", "status"], options2));
2505
2505
  var orcaAccountList = async (runner, options2 = {}) => orcaJson(runner, ["account", "list"], options2);
2506
+ var orcaDiagnosticsMemory = async (runner, options2 = {}) => {
2507
+ try {
2508
+ const result = await orcaJson(runner, ["diagnostics", "memory"], options2);
2509
+ if (!isRecord7(result)) return null;
2510
+ const host = isRecord7(result["host"]) ? result["host"] : {};
2511
+ const availableBytes = host["availableMemory"];
2512
+ if (typeof availableBytes !== "number" || !Number.isFinite(availableBytes) || availableBytes <= 0) return null;
2513
+ const totalBytes = typeof host["totalMemory"] === "number" ? host["totalMemory"] : null;
2514
+ const worktrees = Array.isArray(result["worktrees"]) ? result["worktrees"] : [];
2515
+ const agentRssSamples = worktrees.filter(isRecord7).flatMap((worktree) => Array.isArray(worktree["sessions"]) ? worktree["sessions"] : []).filter(isRecord7).map((session) => session["memory"]).filter((value) => typeof value === "number" && Number.isFinite(value) && value > 0);
2516
+ return { availableBytes, totalBytes, agentRssSamples };
2517
+ } catch {
2518
+ return null;
2519
+ }
2520
+ };
2506
2521
  var parseOrcaWorktreeCreate = (result) => {
2507
2522
  const record3 = isRecord7(result) ? result : {};
2508
2523
  const nested = isRecord7(record3["worktree"]) ? record3["worktree"] : record3;
@@ -2578,6 +2593,12 @@ var orcaTerminalWait = async (runner, input, options2 = {}) => {
2578
2593
  const wait = isRecord7(record3["wait"]) ? record3["wait"] : record3;
2579
2594
  return { satisfied: wait["satisfied"] === true, raw: result };
2580
2595
  };
2596
+ var orcaTerminalScreen = async (runner, input, options2 = {}) => {
2597
+ const result = await orcaJson(runner, ["terminal", "read", "--terminal", input.terminal, "--screen"], options2);
2598
+ const record3 = isRecord7(result) ? isRecord7(result["terminal"]) ? result["terminal"] : result : {};
2599
+ const screen = record3["tail"] ?? record3["screen"] ?? record3["lines"] ?? record3["text"] ?? record3["output"];
2600
+ return Array.isArray(screen) ? screen.map((line2) => isRecord7(line2) ? str(line2["text"], str(line2["line"])) : String(line2)).join("\n") : typeof screen === "string" ? screen : "";
2601
+ };
2581
2602
  var parseOrcaAutomations = (result) => {
2582
2603
  const list2 = isRecord7(result) ? Array.isArray(result["automations"]) ? result["automations"] : Array.isArray(result["items"]) ? result["items"] : [] : Array.isArray(result) ? result : [];
2583
2604
  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);
@@ -3194,8 +3215,8 @@ var isWsl = (platform = process.platform, osRelease = release(), env = process.e
3194
3215
  var assessSlots = (input) => {
3195
3216
  const platform = input.platform ?? process.platform;
3196
3217
  const wsl = isWsl(platform, input.osRelease);
3197
- const freeBytes = input.freeBytes ?? availableMemoryBytes(platform);
3198
- const totalBytes = input.totalBytes ?? totalmem();
3218
+ const freeBytes = input.freeBytes ?? input.orcaMemory?.availableBytes ?? availableMemoryBytes(platform);
3219
+ const totalBytes = input.totalBytes ?? input.orcaMemory?.totalBytes ?? totalmem();
3199
3220
  const sample = input.sample ?? { ...sampleMachine(), memoryUsedPercent: Number(Math.max(0, Math.min(100, (1 - freeBytes / Math.max(1, totalBytes)) * 100)).toFixed(2)) };
3200
3221
  const freeRamGb = Number((freeBytes / 1024 ** 3).toFixed(2));
3201
3222
  const reasons = [];
@@ -3203,9 +3224,11 @@ var assessSlots = (input) => {
3203
3224
  const adaptive = adaptiveConcurrency(ceiling, sample, { warningPercent: input.machine.warningPercent, criticalPercent: input.machine.criticalPercent });
3204
3225
  if (adaptive < ceiling) reasons.push(`machine pressure capped concurrency at ${adaptive} (load ${sample.load1PerCpuPercent}%, memory ${sample.memoryUsedPercent}%)`);
3205
3226
  const reservedBytes = input.machine.minFreeRamGb * 1024 ** 3;
3206
- const perAgentBytes = input.machine.agentRssMb * 1024 ** 2;
3227
+ const measuredAgentBytes = input.orcaMemory?.agentRssSamples.length ? input.orcaMemory.agentRssSamples.reduce((total, value) => total + value, 0) / input.orcaMemory.agentRssSamples.length : null;
3228
+ const perAgentBytes = measuredAgentBytes ?? input.machine.agentRssMb * 1024 ** 2;
3229
+ const perAgentMb = Math.round(perAgentBytes / 1024 ** 2);
3207
3230
  const ramBound = Math.max(0, Math.floor((freeBytes - reservedBytes) / perAgentBytes)) + input.running;
3208
- 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`);
3231
+ 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)" : ""}`);
3209
3232
  let maxAgents = Math.min(adaptive, ramBound);
3210
3233
  if (wsl && maxAgents > input.machine.wslCap) {
3211
3234
  maxAgents = input.machine.wslCap;
@@ -3855,7 +3878,8 @@ var runLoopDoctor = async (input) => {
3855
3878
  push("orca.worktrees", "warning", `worktree ps unavailable: ${workersError}`);
3856
3879
  }
3857
3880
  const running = countRunningWorkers(worktrees);
3858
- const machine = assessSlots({ machine: config.machine, running, platform: input.platform });
3881
+ const orcaMemory = await orcaDiagnosticsMemory(input.runner, orcaOptions2);
3882
+ const machine = assessSlots({ machine: config.machine, running, platform: input.platform, orcaMemory });
3859
3883
  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("; ")}` : ""}`);
3860
3884
  let queue = [];
3861
3885
  let queueError = null;
@@ -4747,11 +4771,12 @@ var gatherLoopState = async (input) => {
4747
4771
  const { config } = input.loaded;
4748
4772
  const person = queueOwner(input.loaded);
4749
4773
  const orca = { bin: config.orca.bin, timeoutMs: config.orca.timeoutMs };
4750
- const [accountList, agentHooks, worktrees, queue] = await Promise.all([
4774
+ const [accountList, agentHooks, worktrees, queue, orcaMemory] = await Promise.all([
4751
4775
  orcaAccountList(input.runner, orca).catch(() => ({})),
4752
4776
  orcaAgentHooks(input.runner, orca).catch(() => ({})),
4753
4777
  orcaWorktrees(input.runner, orca),
4754
- fetchLinearQueue(input.runner, { bin: config.orca.bin, workspaceId: config.linear.workspaceId, teamKey: config.linear.teamKey, assignee: person, filter: config.linear, orca })
4778
+ fetchLinearQueue(input.runner, { bin: config.orca.bin, workspaceId: config.linear.workspaceId, teamKey: config.linear.teamKey, assignee: person, filter: config.linear, orca }),
4779
+ orcaDiagnosticsMemory(input.runner, orca)
4755
4780
  ]);
4756
4781
  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 });
4757
4782
  const availableIds = providers.filter((provider) => provider.available).map((provider) => provider.id);
@@ -4766,7 +4791,7 @@ var gatherLoopState = async (input) => {
4766
4791
  })]))) : {};
4767
4792
  const routing = routeAllRoles(config, providers, extrasByRole);
4768
4793
  const running = countRunningWorkers(worktrees);
4769
- const slots = assessSlots({ machine: config.machine, running, platform: input.platform, ...input.machine });
4794
+ const slots = assessSlots({ machine: config.machine, running, platform: input.platform, orcaMemory, ...input.machine });
4770
4795
  const leases = input.ledger.active();
4771
4796
  const busy = busyIssues(queue, leases, worktrees, person);
4772
4797
  const candidates = queue.filter((issue) => !busy.has(issue.identifier) && (!input.onlyIssue || issue.identifier === input.onlyIssue));
@@ -5278,14 +5303,34 @@ ${JSON.stringify(stored.contract, null, 2)}
5278
5303
  return false;
5279
5304
  }
5280
5305
  };
5306
+ var captureWorkerOutput = async (ctx, terminal2) => {
5307
+ if (!terminal2) return null;
5308
+ try {
5309
+ const screen = (await orcaTerminalScreen(ctx.runner, { terminal: terminal2 }, orcaOptions(ctx.config))).trim();
5310
+ return screen ? screen.slice(-2e3) : null;
5311
+ } catch {
5312
+ return null;
5313
+ }
5314
+ };
5281
5315
  var escalateLinear = async (ctx, record3, kind, body2, actions) => {
5282
5316
  if (ctx.dryRun) {
5283
5317
  actions.push(`would mark ${kind} in Linear and Orca`);
5284
5318
  return;
5285
5319
  }
5320
+ const workerOutput = await captureWorkerOutput(ctx, record3.terminal);
5321
+ const fullBody = workerOutput ? `${body2}
5322
+
5323
+ <details><summary>Worker's last terminal output</summary>
5324
+
5325
+ \`\`\`
5326
+ ${workerOutput}
5327
+ \`\`\`
5328
+
5329
+ </details>` : body2;
5330
+ if (workerOutput) actions.push("captured worker terminal output for the escalation");
5286
5331
  const linear = linearOptions(ctx.config);
5287
5332
  try {
5288
- await linearCommentAdd(ctx.runner, { issue: record3.issue, body: `${body2}
5333
+ await linearCommentAdd(ctx.runner, { issue: record3.issue, body: `${fullBody}
5289
5334
 
5290
5335
  <!-- loop:${kind}:${record3.leaseId} -->`, dedupeKey: `${kind}:${record3.issue}:${record3.leaseId}` }, linear);
5291
5336
  await linearLabelAdd(ctx.runner, { issue: record3.issue, labels: [ctx.config.linear.blockedLabel] }, linear);