cruo-agent 0.1.7 → 0.1.8

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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.7
1
+ 0.1.8
package/dist/cli.js CHANGED
@@ -46228,7 +46228,12 @@ predate cost recording (\`CRA-89\`, 2026-08-29).
46228
46228
  cacheRead: 0,
46229
46229
  cacheWrite: 0,
46230
46230
  cost: 0,
46231
- measured: 0
46231
+ measured: 0,
46232
+ opening: 0,
46233
+ openingMeasured: 0,
46234
+ sysChars: 0,
46235
+ userChars: 0,
46236
+ charsMeasured: 0
46232
46237
  });
46233
46238
  const fold = (a, r) => {
46234
46239
  a.runs += 1;
@@ -46242,6 +46247,16 @@ predate cost recording (\`CRA-89\`, 2026-08-29).
46242
46247
  a.cacheWrite += n(r.cache_write_tokens);
46243
46248
  a.cost += n(r.cost_usd);
46244
46249
  if (r.input_tokens !== null || r.cost_usd !== null) a.measured += 1;
46250
+ const openingKnown = r.opening_context_input_tokens !== null || r.opening_context_cache_read_tokens !== null || r.opening_context_cache_write_tokens !== null;
46251
+ if (openingKnown) {
46252
+ a.opening += n(r.opening_context_input_tokens) + n(r.opening_context_cache_read_tokens) + n(r.opening_context_cache_write_tokens);
46253
+ a.openingMeasured += 1;
46254
+ }
46255
+ if (r.system_prompt_chars !== null || r.user_prompt_chars !== null) {
46256
+ a.sysChars += n(r.system_prompt_chars);
46257
+ a.userChars += n(r.user_prompt_chars);
46258
+ a.charsMeasured += 1;
46259
+ }
46245
46260
  };
46246
46261
  const byAgent = /* @__PURE__ */ new Map();
46247
46262
  const total = blank();
@@ -46253,11 +46268,12 @@ predate cost recording (\`CRA-89\`, 2026-08-29).
46253
46268
  const w = Math.max(12, ...[...byAgent.keys()].map((id) => nameOf(id).length));
46254
46269
  console.log(
46255
46270
  `
46256
- ${pad("agent", w)} ${rpad("runs", 5)} ${rpad("useful", 7)} ${rpad("dead", 5)} ${rpad("time", 7)} ${rpad("in", 8)} ${rpad("out", 8)} ${rpad("cost", 9)}`
46271
+ ${pad("agent", w)} ${rpad("runs", 5)} ${rpad("useful", 7)} ${rpad("dead", 5)} ${rpad("time", 7)} ${rpad("in", 8)} ${rpad("out", 8)} ${rpad("opening", 8)} ${rpad("cost", 9)}`
46257
46272
  );
46258
46273
  for (const [id, a] of [...byAgent].sort((x, y) => y[1].runs - x[1].runs)) {
46274
+ const openingAvg = a.openingMeasured > 0 ? compact(Math.round(a.opening / a.openingMeasured)) : "\u2014";
46259
46275
  console.log(
46260
- ` ${pad(nameOf(id), w)} ${rpad(String(a.runs), 5)} ${rpad(String(a.productive), 7)} ${rpad(String(a.neverRan), 5)} ${rpad(duration3(a.ms), 7)} ${rpad(compact(a.input + a.cacheRead), 8)} ${rpad(compact(a.output), 8)} ${rpad(a.cost > 0 ? `$${a.cost.toFixed(2)}` : "\u2014", 9)}`
46276
+ ` ${pad(nameOf(id), w)} ${rpad(String(a.runs), 5)} ${rpad(String(a.productive), 7)} ${rpad(String(a.neverRan), 5)} ${rpad(duration3(a.ms), 7)} ${rpad(compact(a.input + a.cacheRead), 8)} ${rpad(compact(a.output), 8)} ${rpad(openingAvg, 8)} ${rpad(a.cost > 0 ? `$${a.cost.toFixed(2)}` : "\u2014", 9)}`
46261
46277
  );
46262
46278
  }
46263
46279
  if (!opts.issue) {
@@ -46290,8 +46306,12 @@ predate cost recording (\`CRA-89\`, 2026-08-29).
46290
46306
  console.log(`
46291
46307
  run by run`);
46292
46308
  for (const r of rows) {
46309
+ const openingKnown = r.opening_context_input_tokens !== null || r.opening_context_cache_read_tokens !== null || r.opening_context_cache_write_tokens !== null;
46310
+ const opening = openingKnown ? compact(
46311
+ n(r.opening_context_input_tokens) + n(r.opening_context_cache_read_tokens) + n(r.opening_context_cache_write_tokens)
46312
+ ) : "\u2014";
46293
46313
  console.log(
46294
- ` ${r.started_at.slice(0, 16).replace("T", " ")} ${pad(r.outcome, 13)} ${rpad(duration3(r.duration_ms), 6)} ${rpad(compact(n(r.output_tokens)), 7)} out ${nameOf(r.agent_id)}`
46314
+ ` ${r.started_at.slice(0, 16).replace("T", " ")} ${pad(r.outcome, 13)} ${rpad(duration3(r.duration_ms), 6)} ${rpad(compact(n(r.output_tokens)), 7)} out ${rpad(opening, 7)} opening ${nameOf(r.agent_id)}`
46295
46315
  );
46296
46316
  }
46297
46317
  }
@@ -46318,6 +46338,35 @@ predate cost recording (\`CRA-89\`, 2026-08-29).
46318
46338
  } else {
46319
46339
  console.log("");
46320
46340
  }
46341
+ if (total.openingMeasured === 0) {
46342
+ console.log(
46343
+ ` No opening-context figure: needs --usage AND a harness that reports
46344
+ per-turn usage on its own events, which is stricter than reporting a
46345
+ final total \u2014 some harnesses do the second and not the first.
46346
+ `
46347
+ );
46348
+ } else {
46349
+ const avg = Math.round(total.opening / total.openingMeasured);
46350
+ console.log(
46351
+ ` opening context \u2014 what was loaded before any work happened, not a slice of "in":
46352
+ avg ${compact(avg)} tokens over ${total.openingMeasured} of ${total.runs} run(s) that reported a first turn.`
46353
+ );
46354
+ if (total.charsMeasured > 0) {
46355
+ const sysAvg = Math.round(total.sysChars / total.charsMeasured);
46356
+ const userAvg = Math.round(total.userChars / total.charsMeasured);
46357
+ console.log(
46358
+ ` Known by source: the system prompt averaged ${sysAvg} chars, the user prompt
46359
+ ${userAvg} \u2014 CHARACTERS, not tokens, because no tokenizer runs here, so this is
46360
+ not subtracted from the ${compact(avg)}-token figure above. The rest of the
46361
+ opening context \u2014 CLAUDE.md, the docs it points at, the card and its comments,
46362
+ and the MCP tool schemas \u2014 is read by the harness itself, off a filesystem and
46363
+ a tool list the supervisor cannot see, and is NOT broken down any further.
46364
+ `
46365
+ );
46366
+ } else {
46367
+ console.log("");
46368
+ }
46369
+ }
46321
46370
  }
46322
46371
  var n, pad, rpad, duration3;
46323
46372
  var init_usage = __esm({
@@ -46371,6 +46420,29 @@ function usageIn(text) {
46371
46420
  const measured = Object.values(out).some((v) => v !== void 0);
46372
46421
  return measured ? out : null;
46373
46422
  }
46423
+ function turnUsageIn(line) {
46424
+ let event;
46425
+ try {
46426
+ event = JSON.parse(line);
46427
+ } catch {
46428
+ return null;
46429
+ }
46430
+ if (typeof event !== "object" || event === null) return null;
46431
+ const e = event;
46432
+ if (e.type !== "assistant") return null;
46433
+ const message = e.message;
46434
+ const usage = message?.usage;
46435
+ if (!usage || typeof usage !== "object") return null;
46436
+ const out = {
46437
+ model: typeof message?.model === "string" ? message.model : void 0,
46438
+ inputTokens: num(usage.input_tokens) ?? num(usage.inputTokens),
46439
+ outputTokens: num(usage.output_tokens) ?? num(usage.outputTokens),
46440
+ cacheReadTokens: num(usage.cache_read_input_tokens) ?? num(usage.cacheReadInputTokens),
46441
+ cacheWriteTokens: num(usage.cache_creation_input_tokens) ?? num(usage.cacheCreationInputTokens)
46442
+ };
46443
+ const measured = Object.values(out).some((v) => v !== void 0);
46444
+ return measured ? out : null;
46445
+ }
46374
46446
  function renderEvent(line) {
46375
46447
  let event;
46376
46448
  try {
@@ -46488,6 +46560,17 @@ var init_pacing = __esm({
46488
46560
  }
46489
46561
  });
46490
46562
 
46563
+ // src/queries.ts
46564
+ function blocksAgentPickup(state) {
46565
+ return state.requires_human === true;
46566
+ }
46567
+ var init_queries = __esm({
46568
+ "src/queries.ts"() {
46569
+ "use strict";
46570
+ init_dist();
46571
+ }
46572
+ });
46573
+
46491
46574
  // src/worktree.ts
46492
46575
  import { execFile as execFile2 } from "node:child_process";
46493
46576
  import { mkdir as mkdir2, readdir as readdir2, rm as rm2, stat } from "node:fs/promises";
@@ -46680,6 +46763,12 @@ async function pollAssigned(ctx) {
46680
46763
  const project = byProject.get(issue2.project_id);
46681
46764
  if (!state || !project) return [];
46682
46765
  if (options.project && project.key !== options.project) return [];
46766
+ if (blocksAgentPickup(state)) {
46767
+ log(
46768
+ ` ${project.key}-${issue2.number} is in ${state.name}, which requires a human \u2014 not starting`
46769
+ );
46770
+ return [];
46771
+ }
46683
46772
  if (state.category === "completed" || state.category === "canceled") return [];
46684
46773
  const said = lastSaid.get(issue2.id);
46685
46774
  if (said && issue2.updated_at && said > issue2.updated_at) return [];
@@ -46714,6 +46803,12 @@ async function poll(ctx, identity) {
46714
46803
  const state = byState.get(issue2.status_id);
46715
46804
  if (!project || !state) return [];
46716
46805
  if (options.project && project.key !== options.project) return [];
46806
+ if (blocksAgentPickup(state)) {
46807
+ log(
46808
+ ` ${project.key}-${issue2.number} is in ${state.name}, which requires a human \u2014 not starting`
46809
+ );
46810
+ return [];
46811
+ }
46717
46812
  return [
46718
46813
  {
46719
46814
  ref: `${project.key}-${issue2.number}`,
@@ -47005,9 +47100,11 @@ async function invokeHarness(ctx, identity, hit, worktree) {
47005
47100
  options.allow.split(",").map((t) => t.trim()).filter((t) => t !== "" && !t.startsWith("mcp__")).map((t) => t.replace(/\(.*$/, ""))
47006
47101
  )
47007
47102
  ].join(",");
47103
+ const userPromptText = userPrompt(hit);
47104
+ const systemPromptText = systemPrompt(ctx, identity, worktree);
47008
47105
  const args = [
47009
47106
  "-p",
47010
- userPrompt(hit),
47107
+ userPromptText,
47011
47108
  "--mcp-config",
47012
47109
  mcpConfig,
47013
47110
  // Both, always. See the note above: without `--restricted` the harness
@@ -47023,7 +47120,7 @@ async function invokeHarness(ctx, identity, hit, worktree) {
47023
47120
  "--allowedTools",
47024
47121
  options.allow,
47025
47122
  "--system-prompt",
47026
- systemPrompt(ctx, identity, worktree),
47123
+ systemPromptText,
47027
47124
  ...options.model ? ["--model", options.model] : [],
47028
47125
  // `--verbose` is not optional alongside stream-json: without it the harness
47029
47126
  // emits only the final result and the live view is a blank terminal for the
@@ -47045,6 +47142,7 @@ async function invokeHarness(ctx, identity, hit, worktree) {
47045
47142
  let tail = "";
47046
47143
  let head2 = "";
47047
47144
  let pending = "";
47145
+ let openingContext = null;
47048
47146
  const addHead = (text) => {
47049
47147
  if (head2.length < 2048) head2 += text.slice(0, 2048 - head2.length);
47050
47148
  };
@@ -47063,6 +47161,7 @@ async function invokeHarness(ctx, identity, hit, worktree) {
47063
47161
  for (const line of lines) {
47064
47162
  if (line.trim() === "") continue;
47065
47163
  tail = line;
47164
+ if (openingContext === null) openingContext = turnUsageIn(line);
47066
47165
  const rendered = renderEvent(line);
47067
47166
  if (rendered.text) addHead(rendered.text + "\n");
47068
47167
  if (rendered.show) process.stdout.write(rendered.show + "\n");
@@ -47086,13 +47185,17 @@ async function invokeHarness(ctx, identity, hit, worktree) {
47086
47185
  durationMs: Date.now() - startedAtMs,
47087
47186
  usage: null,
47088
47187
  killed,
47089
- stdoutHead: headFor(head2)
47188
+ stdoutHead: headFor(head2),
47189
+ openingContext: null,
47190
+ systemPromptChars: systemPromptText.length,
47191
+ userPromptChars: userPromptText.length
47090
47192
  });
47091
47193
  });
47092
47194
  child.on("close", (code) => {
47093
47195
  clearTimeout(timer);
47094
47196
  if (streaming && pending.trim() !== "") {
47095
47197
  tail = pending;
47198
+ if (openingContext === null) openingContext = turnUsageIn(pending);
47096
47199
  const rendered = renderEvent(pending);
47097
47200
  if (rendered.text) addHead(rendered.text + "\n");
47098
47201
  if (rendered.show) process.stdout.write(rendered.show + "\n");
@@ -47105,7 +47208,10 @@ async function invokeHarness(ctx, identity, hit, worktree) {
47105
47208
  durationMs: Date.now() - startedAtMs,
47106
47209
  usage: usageIn(tail),
47107
47210
  killed,
47108
- stdoutHead: headFor(head2)
47211
+ stdoutHead: headFor(head2),
47212
+ openingContext,
47213
+ systemPromptChars: systemPromptText.length,
47214
+ userPromptChars: userPromptText.length
47109
47215
  });
47110
47216
  });
47111
47217
  });
@@ -47207,6 +47313,16 @@ function announce(status) {
47207
47313
  }
47208
47314
  })();
47209
47315
  }
47316
+ function beatWhileBusy(ctx, deadTicks) {
47317
+ const timer = setInterval(() => {
47318
+ void beat(ctx, { holding: lastHolding, deadTicks, intervalMs: options.intervalMs }).catch(
47319
+ () => {
47320
+ }
47321
+ );
47322
+ }, options.intervalMs);
47323
+ timer.unref?.();
47324
+ return () => clearInterval(timer);
47325
+ }
47210
47326
  async function recordRun(ctx, hit, run, outcome) {
47211
47327
  const { error: error51 } = await ctx.client.from("harness_runs").insert({
47212
47328
  agent_id: ctx.userId,
@@ -47223,7 +47339,12 @@ async function recordRun(ctx, hit, run, outcome) {
47223
47339
  output_tokens: run.usage?.outputTokens ?? null,
47224
47340
  cache_read_tokens: run.usage?.cacheReadTokens ?? null,
47225
47341
  cache_write_tokens: run.usage?.cacheWriteTokens ?? null,
47226
- cost_usd: run.usage?.costUsd ?? null
47342
+ cost_usd: run.usage?.costUsd ?? null,
47343
+ opening_context_input_tokens: run.openingContext?.inputTokens ?? null,
47344
+ opening_context_cache_read_tokens: run.openingContext?.cacheReadTokens ?? null,
47345
+ opening_context_cache_write_tokens: run.openingContext?.cacheWriteTokens ?? null,
47346
+ system_prompt_chars: run.systemPromptChars,
47347
+ user_prompt_chars: run.userPromptChars
47227
47348
  });
47228
47349
  if (error51 && !warnedAboutSpendTable) {
47229
47350
  warnedAboutSpendTable = true;
@@ -47314,6 +47435,7 @@ async function tick(ctx, identity, deadTicks, allowance) {
47314
47435
  let run;
47315
47436
  let committed = 0;
47316
47437
  let salvaged = null;
47438
+ const stopBusyBeat = beatWhileBusy(ctx, deadTicks);
47317
47439
  try {
47318
47440
  run = await invokeHarness(ctx, identity, hit, worktree);
47319
47441
  if (worktree) committed = await worktree.commits();
@@ -47336,6 +47458,7 @@ async function tick(ctx, identity, deadTicks, allowance) {
47336
47458
  );
47337
47459
  }
47338
47460
  } finally {
47461
+ stopBusyBeat();
47339
47462
  await release(ctx, hit);
47340
47463
  }
47341
47464
  const code = run.code;
@@ -47571,6 +47694,7 @@ var init_supervisor = __esm({
47571
47694
  init_harness_signal();
47572
47695
  init_process();
47573
47696
  init_pacing();
47697
+ init_queries();
47574
47698
  init_worktree();
47575
47699
  argv = process.argv.slice(2);
47576
47700
  flag = (name) => flagIn(argv, name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cruo-agent",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Run a Cruo agent: it watches your board, picks up the cards you assign it, and works them.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://cruo.space/docs#agents",