cruo-agent 0.1.15 → 0.1.16

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.
Files changed (4) hide show
  1. package/dist/VERSION +1 -1
  2. package/dist/cli.js +266 -78
  3. package/dist/index.js +38253 -2877
  4. package/package.json +1 -1
package/dist/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.15
1
+ 0.1.16
package/dist/cli.js CHANGED
@@ -15233,6 +15233,15 @@ var init_identity = __esm({
15233
15233
  }
15234
15234
  });
15235
15235
 
15236
+ // ../../packages/core/dist/attachments.js
15237
+ var MAX_ATTACHMENT_BYTES;
15238
+ var init_attachments = __esm({
15239
+ "../../packages/core/dist/attachments.js"() {
15240
+ "use strict";
15241
+ MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
15242
+ }
15243
+ });
15244
+
15236
15245
  // ../../packages/core/dist/effort.js
15237
15246
  var init_effort = __esm({
15238
15247
  "../../packages/core/dist/effort.js"() {
@@ -15298,6 +15307,7 @@ var init_dist = __esm({
15298
15307
  init_schemas3();
15299
15308
  init_rank();
15300
15309
  init_identity();
15310
+ init_attachments();
15301
15311
  init_effort();
15302
15312
  init_plans();
15303
15313
  init_reports();
@@ -36793,16 +36803,41 @@ var init_dist6 = __esm({
36793
36803
  });
36794
36804
 
36795
36805
  // ../../packages/mcp-kit/dist/context.js
36806
+ function decodeJwtExpiry(token) {
36807
+ const parts = token.split(".");
36808
+ if (parts.length !== 3)
36809
+ return null;
36810
+ try {
36811
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
36812
+ return typeof payload.exp === "number" ? payload.exp * 1e3 : null;
36813
+ } catch {
36814
+ return null;
36815
+ }
36816
+ }
36817
+ function isExpiredSessionError(error51) {
36818
+ if (!error51)
36819
+ return false;
36820
+ if (error51.code === "PGRST301" || error51.code === "PGRST303")
36821
+ return true;
36822
+ const message = (error51.message ?? "").toLowerCase();
36823
+ return message.includes("jwt expired") || message.includes("jwt is expired") || message.includes("invalid jwt") || message.includes("jws error") || message.includes("token is expired");
36824
+ }
36825
+ async function resolveContext(options2) {
36826
+ return resolve(options2);
36827
+ }
36828
+ function refreshBy(ctx) {
36829
+ return ctx.expiresAt != null ? ctx.expiresAt - 6e4 : Date.now() + ASSUMED_SESSION_LIFETIME_MS;
36830
+ }
36796
36831
  function createContextResolver(options2) {
36797
36832
  let cached2 = null;
36798
36833
  let inflight = null;
36799
36834
  return async function getContext2() {
36800
- if (cached2 && cached2.expiresAt > Date.now() + 6e4)
36835
+ if (cached2 && cached2.refreshBy > Date.now())
36801
36836
  return cached2.ctx;
36802
36837
  if (inflight)
36803
36838
  return inflight;
36804
36839
  const attempt = resolve(options2).then((ctx) => {
36805
- cached2 = { ctx, expiresAt: Date.now() + 50 * 6e4 };
36840
+ cached2 = { ctx, refreshBy: refreshBy(ctx) };
36806
36841
  return ctx;
36807
36842
  });
36808
36843
  inflight = attempt;
@@ -36928,13 +36963,13 @@ function createRemoteContextResolver(options2) {
36928
36963
  let cached2 = null;
36929
36964
  let inflight = null;
36930
36965
  return async function getContext2() {
36931
- if (cached2 && cached2.expiresAt > Date.now() + 6e4)
36966
+ if (cached2 && cached2.refreshBy > Date.now())
36932
36967
  return cached2.ctx;
36933
36968
  if (inflight)
36934
36969
  return inflight;
36935
36970
  const attempt = fetchDelegatedSession(options2.endpoint, options2.token).then((session) => {
36936
36971
  const ctx = contextFromSession(options2.schema, session);
36937
- cached2 = { ctx, expiresAt: Date.now() + 50 * 6e4 };
36972
+ cached2 = { ctx, refreshBy: refreshBy(ctx) };
36938
36973
  return ctx;
36939
36974
  });
36940
36975
  inflight = attempt;
@@ -36974,7 +37009,8 @@ function contextFromSession(schema, session) {
36974
37009
  userId: session.userId,
36975
37010
  email: session.email,
36976
37011
  workspaceId: session.workspaceId,
36977
- workspaceName: session.workspaceName
37012
+ workspaceName: session.workspaceName,
37013
+ expiresAt: decodeJwtExpiry(session.accessToken)
36978
37014
  };
36979
37015
  }
36980
37016
  async function mintContext(options2) {
@@ -36985,7 +37021,8 @@ async function mintContext(options2) {
36985
37021
  userId: principal.userId,
36986
37022
  email: principal.email,
36987
37023
  workspaceId: principal.workspaceId,
36988
- workspaceName: principal.workspaceName
37024
+ workspaceName: principal.workspaceName,
37025
+ expiresAt: decodeJwtExpiry(accessToken)
36989
37026
  };
36990
37027
  }
36991
37028
  async function resolve(options2) {
@@ -36993,7 +37030,7 @@ async function resolve(options2) {
36993
37030
  const principal = await authenticate({ config: config3, token, product, mintedAt });
36994
37031
  return mintContext({ config: config3, principal, schema });
36995
37032
  }
36996
- var TokenRefused, sessionInFlight, MINT_ATTEMPTS, DELEGATED_SESSION_ENV;
37033
+ var TokenRefused, ASSUMED_SESSION_LIFETIME_MS, sessionInFlight, MINT_ATTEMPTS, DELEGATED_SESSION_ENV;
36997
37034
  var init_context = __esm({
36998
37035
  "../../packages/mcp-kit/dist/context.js"() {
36999
37036
  "use strict";
@@ -37005,6 +37042,7 @@ var init_context = __esm({
37005
37042
  this.name = "TokenRefused";
37006
37043
  }
37007
37044
  };
37045
+ ASSUMED_SESSION_LIFETIME_MS = 50 * 6e4;
37008
37046
  sessionInFlight = /* @__PURE__ */ new Map();
37009
37047
  MINT_ATTEMPTS = 3;
37010
37048
  DELEGATED_SESSION_ENV = "CRUO_SESSION";
@@ -46270,6 +46308,11 @@ function getContext() {
46270
46308
  });
46271
46309
  return stdioResolver();
46272
46310
  }
46311
+ async function refreshContext() {
46312
+ const token = readToken({ mintedAt: MINTED_AT });
46313
+ const endpoint = sessionEndpoint();
46314
+ return endpoint ? contextFromSession("pm", await fetchDelegatedSession(endpoint, token)) : resolveContext({ config: config2, token, product: "projects", schema: "pm", mintedAt: MINTED_AT });
46315
+ }
46273
46316
  function sessionEndpoint() {
46274
46317
  const raw = process.env.CRUO_SESSION_URL;
46275
46318
  if (raw && raw.trim()) return raw.trim();
@@ -46527,6 +46570,56 @@ var init_usage = __esm({
46527
46570
  }
46528
46571
  });
46529
46572
 
46573
+ // src/session-renewal.ts
46574
+ async function withFreshSession(ctx, attempt, renewal) {
46575
+ const first = await attempt();
46576
+ if (!first.error || !isExpiredSessionError(first.error)) {
46577
+ warnedThisOutage = false;
46578
+ return first;
46579
+ }
46580
+ try {
46581
+ const fresh = await renewal.renew();
46582
+ ctx.client = fresh.client;
46583
+ ctx.userId = fresh.userId;
46584
+ ctx.email = fresh.email;
46585
+ ctx.workspaceId = fresh.workspaceId;
46586
+ ctx.workspaceName = fresh.workspaceName;
46587
+ ctx.expiresAt = fresh.expiresAt;
46588
+ warnedThisOutage = false;
46589
+ return await attempt();
46590
+ } catch (error51) {
46591
+ if (!warnedThisOutage) {
46592
+ warnedThisOutage = true;
46593
+ renewal.log(
46594
+ `! this agent's session expired and could not be renewed: ${error51 instanceof Error ? error51.message : String(error51)}`
46595
+ );
46596
+ renewal.log(
46597
+ ` board writes will keep failing until this is fixed \u2014 the run in progress is not killed for it.`
46598
+ );
46599
+ }
46600
+ return first;
46601
+ }
46602
+ }
46603
+ var warnedThisOutage;
46604
+ var init_session_renewal = __esm({
46605
+ "src/session-renewal.ts"() {
46606
+ "use strict";
46607
+ init_dist7();
46608
+ warnedThisOutage = false;
46609
+ }
46610
+ });
46611
+
46612
+ // src/claim.ts
46613
+ function interpretClaim(data, error51) {
46614
+ if (error51) return { taken: false, heldElsewhere: false, message: error51.message };
46615
+ return data === true ? { taken: true } : { taken: false, heldElsewhere: true };
46616
+ }
46617
+ var init_claim = __esm({
46618
+ "src/claim.ts"() {
46619
+ "use strict";
46620
+ }
46621
+ });
46622
+
46530
46623
  // src/harness-signal.ts
46531
46624
  function refusalIn(stdout) {
46532
46625
  if (!stdout.trim()) return null;
@@ -46725,6 +46818,48 @@ var init_pacing = __esm({
46725
46818
  }
46726
46819
  });
46727
46820
 
46821
+ // src/wake-clock.ts
46822
+ function tickWakeClock(clock, now, tickMs, timeoutMs) {
46823
+ const elapsed = now - clock.lastTickAt;
46824
+ const sleepThresholdMs = tickMs * 4;
46825
+ const asleep = elapsed > sleepThresholdMs;
46826
+ const creditedMs = asleep ? tickMs : Math.max(elapsed, 0);
46827
+ const awakeMs = clock.awakeMs + creditedMs;
46828
+ const wokeAfterMs = asleep ? elapsed - tickMs : null;
46829
+ return {
46830
+ next: {
46831
+ awakeMs,
46832
+ lastTickAt: now,
46833
+ sawSleep: clock.sawSleep || asleep
46834
+ },
46835
+ wokeAfterMs,
46836
+ timedOut: awakeMs >= timeoutMs
46837
+ };
46838
+ }
46839
+ function killSaysNothingAboutCard(run) {
46840
+ return run.killed && run.sawSleep;
46841
+ }
46842
+ function humanDuration(ms2) {
46843
+ const totalSeconds = Math.max(0, Math.round(ms2 / 1e3));
46844
+ const h = Math.floor(totalSeconds / 3600);
46845
+ const m = Math.floor(totalSeconds % 3600 / 60);
46846
+ const s = totalSeconds % 60;
46847
+ if (h > 0) return `${h}h${m}m`;
46848
+ if (m > 0) return `${m}m${s}s`;
46849
+ return `${s}s`;
46850
+ }
46851
+ var freshWakeClock;
46852
+ var init_wake_clock = __esm({
46853
+ "src/wake-clock.ts"() {
46854
+ "use strict";
46855
+ freshWakeClock = (now) => ({
46856
+ awakeMs: 0,
46857
+ lastTickAt: now,
46858
+ sawSleep: false
46859
+ });
46860
+ }
46861
+ });
46862
+
46728
46863
  // src/queries.ts
46729
46864
  function blocksAgentPickup(state) {
46730
46865
  return state.requires_human === true;
@@ -47042,15 +47177,19 @@ async function pollMentions(ctx, identity) {
47042
47177
  }
47043
47178
  async function claim(ctx, hit) {
47044
47179
  const ttlMs = options.harnessTimeoutMs + 3e4;
47045
- const { data, error: error51 } = await ctx.client.schema("public").rpc("claim_issue", {
47046
- issue: hit.issue.id,
47047
- ttl_ms: ttlMs
47048
- });
47049
- if (error51) {
47050
- log(` could not claim ${hit.ref}: ${error51.message} \u2014 leaving it`);
47051
- return false;
47180
+ const { data, error: error51 } = await withFreshSession(
47181
+ ctx,
47182
+ () => ctx.client.schema("public").rpc("claim_issue", {
47183
+ issue: hit.issue.id,
47184
+ ttl_ms: ttlMs
47185
+ }),
47186
+ sessionRenewal
47187
+ );
47188
+ const outcome = interpretClaim(data, error51);
47189
+ if (!outcome.taken && !outcome.heldElsewhere) {
47190
+ log(` could not claim ${hit.ref}: ${outcome.message} \u2014 leaving it`);
47052
47191
  }
47053
- return data === true;
47192
+ return outcome;
47054
47193
  }
47055
47194
  async function claimedElsewhere(ctx, hits) {
47056
47195
  if (hits.length === 0) return /* @__PURE__ */ new Set();
@@ -47062,12 +47201,20 @@ async function claimedElsewhere(ctx, hits) {
47062
47201
  return new Set((data ?? []).map((r) => r.issue_id));
47063
47202
  }
47064
47203
  async function release(ctx, hit) {
47065
- const { error: error51 } = await ctx.client.schema("pm").from("issue_claims").delete().eq("issue_id", hit.issue.id).eq("claimed_by", ctx.userId);
47204
+ const { error: error51 } = await withFreshSession(
47205
+ ctx,
47206
+ () => ctx.client.schema("pm").from("issue_claims").delete().eq("issue_id", hit.issue.id).eq("claimed_by", ctx.userId),
47207
+ sessionRenewal
47208
+ );
47066
47209
  if (error51) log(` could not release ${hit.ref}: ${error51.message}`);
47067
47210
  }
47068
47211
  async function markRead(ctx, notificationIds) {
47069
47212
  if (notificationIds.length === 0) return;
47070
- const { error: error51 } = await ctx.client.schema("public").from("notifications").update({ read_at: (/* @__PURE__ */ new Date()).toISOString() }).in("id", notificationIds);
47213
+ const { error: error51 } = await withFreshSession(
47214
+ ctx,
47215
+ () => ctx.client.schema("public").from("notifications").update({ read_at: (/* @__PURE__ */ new Date()).toISOString() }).in("id", notificationIds),
47216
+ sessionRenewal
47217
+ );
47071
47218
  if (error51) log(` could not mark ${notificationIds.length} mention(s) read: ${error51.message}`);
47072
47219
  }
47073
47220
  async function writeMcpConfig() {
@@ -47351,14 +47498,29 @@ async function invokeHarness(ctx, identity, hit, worktree) {
47351
47498
  });
47352
47499
  child.stderr?.on("data", (c) => process.stderr.write(c));
47353
47500
  let killed = false;
47354
- const timer = setTimeout(() => {
47355
- killed = true;
47356
- log(` harness exceeded ${options.harnessTimeoutMs / 1e3}s \u2014 killing it`);
47357
- child.kill("SIGTERM");
47358
- setTimeout(() => child.kill("SIGKILL"), 1e4).unref();
47359
- }, options.harnessTimeoutMs);
47501
+ const WAKE_TICK_MS = 5e3;
47502
+ let wakeClock = freshWakeClock(Date.now());
47503
+ const wakeTimer = setInterval(() => {
47504
+ const step = tickWakeClock(wakeClock, Date.now(), WAKE_TICK_MS, options.harnessTimeoutMs);
47505
+ wakeClock = step.next;
47506
+ if (step.wokeAfterMs !== null) {
47507
+ log(` machine slept for ${humanDuration(step.wokeAfterMs)} \u2014 not counted against ${hit.ref}`);
47508
+ void claim(ctx, hit).catch(() => {
47509
+ });
47510
+ }
47511
+ if (step.timedOut && !killed) {
47512
+ killed = true;
47513
+ clearInterval(wakeTimer);
47514
+ log(
47515
+ ` harness exceeded ${options.harnessTimeoutMs / 1e3}s awake` + (wakeClock.sawSleep ? " (excluding sleep) \u2014 killing it" : " \u2014 killing it")
47516
+ );
47517
+ child.kill("SIGTERM");
47518
+ setTimeout(() => child.kill("SIGKILL"), 1e4).unref();
47519
+ }
47520
+ }, WAKE_TICK_MS);
47521
+ wakeTimer.unref?.();
47360
47522
  child.on("error", (e) => {
47361
- clearTimeout(timer);
47523
+ clearInterval(wakeTimer);
47362
47524
  log(` harness failed to start: ${e.message}`);
47363
47525
  resolve2({
47364
47526
  code: 127,
@@ -47367,6 +47529,7 @@ async function invokeHarness(ctx, identity, hit, worktree) {
47367
47529
  durationMs: Date.now() - startedAtMs,
47368
47530
  usage: null,
47369
47531
  killed,
47532
+ sawSleep: wakeClock.sawSleep,
47370
47533
  stdoutHead: headFor(head2),
47371
47534
  openingContext: null,
47372
47535
  systemPromptChars: systemPromptText.length,
@@ -47374,7 +47537,7 @@ async function invokeHarness(ctx, identity, hit, worktree) {
47374
47537
  });
47375
47538
  });
47376
47539
  child.on("close", (code) => {
47377
- clearTimeout(timer);
47540
+ clearInterval(wakeTimer);
47378
47541
  if (streaming && pending.trim() !== "") {
47379
47542
  tail = pending;
47380
47543
  if (openingContext === null) openingContext = turnUsageIn(pending);
@@ -47390,6 +47553,7 @@ async function invokeHarness(ctx, identity, hit, worktree) {
47390
47553
  durationMs: Date.now() - startedAtMs,
47391
47554
  usage: usageIn(tail),
47392
47555
  killed,
47556
+ sawSleep: wakeClock.sawSleep,
47393
47557
  stdoutHead: headFor(head2),
47394
47558
  openingContext,
47395
47559
  systemPromptChars: systemPromptText.length,
@@ -47414,25 +47578,33 @@ async function loadAttempts(ctx, hits) {
47414
47578
  }
47415
47579
  async function recordAttempt(ctx, hit, outcome, previous) {
47416
47580
  const attempts = previous + 1;
47417
- const { error: error51 } = await ctx.client.from("issue_attempts").upsert(
47418
- {
47419
- issue_id: hit.issue.id,
47420
- agent_id: ctx.userId,
47421
- reason: hit.reason,
47422
- attempts,
47423
- last_tried_at: (/* @__PURE__ */ new Date()).toISOString(),
47424
- last_outcome: outcome,
47425
- // Stamped at the moment the budget runs out, so the board can say when
47426
- // the automation gave up rather than only that it did.
47427
- set_aside_at: attempts >= options.maxAttempts ? (/* @__PURE__ */ new Date()).toISOString() : null
47428
- },
47429
- { onConflict: "issue_id,agent_id,reason" }
47581
+ const { error: error51 } = await withFreshSession(
47582
+ ctx,
47583
+ () => ctx.client.from("issue_attempts").upsert(
47584
+ {
47585
+ issue_id: hit.issue.id,
47586
+ agent_id: ctx.userId,
47587
+ reason: hit.reason,
47588
+ attempts,
47589
+ last_tried_at: (/* @__PURE__ */ new Date()).toISOString(),
47590
+ last_outcome: outcome,
47591
+ // Stamped at the moment the budget runs out, so the board can say when
47592
+ // the automation gave up rather than only that it did.
47593
+ set_aside_at: attempts >= options.maxAttempts ? (/* @__PURE__ */ new Date()).toISOString() : null
47594
+ },
47595
+ { onConflict: "issue_id,agent_id,reason" }
47596
+ ),
47597
+ sessionRenewal
47430
47598
  );
47431
47599
  if (error51) log(` could not record the attempt on ${hit.ref}: ${error51.message}`);
47432
47600
  return attempts;
47433
47601
  }
47434
47602
  async function clearAttempts(ctx, hit) {
47435
- const { error: error51 } = await ctx.client.from("issue_attempts").delete().eq("issue_id", hit.issue.id).eq("agent_id", ctx.userId).eq("reason", hit.reason);
47603
+ const { error: error51 } = await withFreshSession(
47604
+ ctx,
47605
+ () => ctx.client.from("issue_attempts").delete().eq("issue_id", hit.issue.id).eq("agent_id", ctx.userId).eq("reason", hit.reason),
47606
+ sessionRenewal
47607
+ );
47436
47608
  if (error51) log(` could not clear attempts on ${hit.ref}: ${error51.message}`);
47437
47609
  }
47438
47610
  async function repliedSince(ctx, hit, since2) {
@@ -47464,20 +47636,24 @@ async function publish(ctx, hit, worktree) {
47464
47636
  }
47465
47637
  }
47466
47638
  async function beat(ctx, fields) {
47467
- const { error: error51 } = await ctx.client.from("agent_heartbeats").upsert(
47468
- {
47469
- user_id: ctx.userId,
47470
- workspace_id: ctx.workspaceId,
47471
- last_seen_at: (/* @__PURE__ */ new Date()).toISOString(),
47472
- holding: fields.holding,
47473
- dead_ticks: fields.deadTicks,
47474
- poll_interval_ms: fields.intervalMs,
47475
- // CRA-102. Without this the board cannot tell an agent someone paused
47476
- // from one that is wading through a backlog, nor one stopped on purpose
47477
- // from one whose laptop shut its lid all of them simply went quiet.
47478
- status: fields.status ?? (paused ? "paused" : "running")
47479
- },
47480
- { onConflict: "user_id,workspace_id" }
47639
+ const { error: error51 } = await withFreshSession(
47640
+ ctx,
47641
+ () => ctx.client.from("agent_heartbeats").upsert(
47642
+ {
47643
+ user_id: ctx.userId,
47644
+ workspace_id: ctx.workspaceId,
47645
+ last_seen_at: (/* @__PURE__ */ new Date()).toISOString(),
47646
+ holding: fields.holding,
47647
+ dead_ticks: fields.deadTicks,
47648
+ poll_interval_ms: fields.intervalMs,
47649
+ // CRA-102. Without this the board cannot tell an agent someone paused
47650
+ // from one that is wading through a backlog, nor one stopped on purpose
47651
+ // from one whose laptop shut its lid — all of them simply went quiet.
47652
+ status: fields.status ?? (paused ? "paused" : "running")
47653
+ },
47654
+ { onConflict: "user_id,workspace_id" }
47655
+ ),
47656
+ sessionRenewal
47481
47657
  );
47482
47658
  if (error51) log(` could not record a heartbeat: ${error51.message}`);
47483
47659
  lastHolding = fields.holding;
@@ -47506,28 +47682,32 @@ function beatWhileBusy(ctx, deadTicks) {
47506
47682
  return () => clearInterval(timer);
47507
47683
  }
47508
47684
  async function recordRun(ctx, hit, run, outcome) {
47509
- const { error: error51 } = await ctx.client.from("harness_runs").insert({
47510
- agent_id: ctx.userId,
47511
- workspace_id: ctx.workspaceId,
47512
- issue_id: hit.issue.id,
47513
- reason: hit.reason,
47514
- duration_ms: run.durationMs,
47515
- exit_code: run.code,
47516
- stdout_bytes: run.stdoutBytes,
47517
- stdout_head: run.stdoutHead,
47518
- outcome,
47519
- model: run.usage?.model ?? null,
47520
- input_tokens: run.usage?.inputTokens ?? null,
47521
- output_tokens: run.usage?.outputTokens ?? null,
47522
- cache_read_tokens: run.usage?.cacheReadTokens ?? null,
47523
- cache_write_tokens: run.usage?.cacheWriteTokens ?? null,
47524
- cost_usd: run.usage?.costUsd ?? null,
47525
- opening_context_input_tokens: run.openingContext?.inputTokens ?? null,
47526
- opening_context_cache_read_tokens: run.openingContext?.cacheReadTokens ?? null,
47527
- opening_context_cache_write_tokens: run.openingContext?.cacheWriteTokens ?? null,
47528
- system_prompt_chars: run.systemPromptChars,
47529
- user_prompt_chars: run.userPromptChars
47530
- });
47685
+ const { error: error51 } = await withFreshSession(
47686
+ ctx,
47687
+ () => ctx.client.from("harness_runs").insert({
47688
+ agent_id: ctx.userId,
47689
+ workspace_id: ctx.workspaceId,
47690
+ issue_id: hit.issue.id,
47691
+ reason: hit.reason,
47692
+ duration_ms: run.durationMs,
47693
+ exit_code: run.code,
47694
+ stdout_bytes: run.stdoutBytes,
47695
+ stdout_head: run.stdoutHead,
47696
+ outcome,
47697
+ model: run.usage?.model ?? null,
47698
+ input_tokens: run.usage?.inputTokens ?? null,
47699
+ output_tokens: run.usage?.outputTokens ?? null,
47700
+ cache_read_tokens: run.usage?.cacheReadTokens ?? null,
47701
+ cache_write_tokens: run.usage?.cacheWriteTokens ?? null,
47702
+ cost_usd: run.usage?.costUsd ?? null,
47703
+ opening_context_input_tokens: run.openingContext?.inputTokens ?? null,
47704
+ opening_context_cache_read_tokens: run.openingContext?.cacheReadTokens ?? null,
47705
+ opening_context_cache_write_tokens: run.openingContext?.cacheWriteTokens ?? null,
47706
+ system_prompt_chars: run.systemPromptChars,
47707
+ user_prompt_chars: run.userPromptChars
47708
+ }),
47709
+ sessionRenewal
47710
+ );
47531
47711
  if (error51 && !warnedAboutSpendTable) {
47532
47712
  warnedAboutSpendTable = true;
47533
47713
  log(` (not recording run costs: ${error51.message})`);
@@ -47597,8 +47777,9 @@ async function tick(ctx, identity, deadTicks, allowance) {
47597
47777
  invoked += 1;
47598
47778
  const before = { status: hit.issue.status_id, assignee: hit.issue.assignee_id };
47599
47779
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
47600
- if (!await claim(ctx, hit)) {
47601
- log(` ${hit.ref} is being worked by another agent \u2014 skipping`);
47780
+ const claimed = await claim(ctx, hit);
47781
+ if (!claimed.taken) {
47782
+ if (claimed.heldElsewhere) log(` ${hit.ref} is being worked by another agent \u2014 skipping`);
47602
47783
  continue;
47603
47784
  }
47604
47785
  let worktree = null;
@@ -47679,6 +47860,9 @@ async function tick(ctx, identity, deadTicks, allowance) {
47679
47860
  } else {
47680
47861
  log(` ${hit.ref} handed on (harness exit ${code})`);
47681
47862
  }
47863
+ } else if (killSaysNothingAboutCard(run)) {
47864
+ const note = salvaged ? `killed after the machine slept mid-run (exit ${code}); cut short, work saved as ${salvaged.slice(0, 8)}` : `killed after the machine slept mid-run (exit ${code})`;
47865
+ log(` ${hit.ref} ${note} \u2014 not counted against this issue`);
47682
47866
  } else {
47683
47867
  const what = hit.reason === "mention" ? "no reply written" : "unchanged";
47684
47868
  const note = salvaged ? `${what} (exit ${code}); cut short, work saved as ${salvaged.slice(0, 8)}` : `${what} (exit ${code})`;
@@ -47910,7 +48094,7 @@ to catch: a second supervisor nothing could see or stop.
47910
48094
  if (stopping) return;
47911
48095
  }
47912
48096
  }
47913
- var argv, flag, opt, num2, ms, readOptions, options, log, worktreeConfig, lastRefusal, CONFIG_DIR_PREFIX, CRUO_OWNED_ENV, HEAD_STORED, lastHolding, warnedAboutSpendTable, stopping, paused, stopRequestedAt, currentRun, pendingWait;
48097
+ var argv, flag, opt, num2, ms, readOptions, options, log, sessionRenewal, worktreeConfig, lastRefusal, CONFIG_DIR_PREFIX, CRUO_OWNED_ENV, HEAD_STORED, lastHolding, warnedAboutSpendTable, stopping, paused, stopRequestedAt, currentRun, pendingWait;
47914
48098
  var init_supervisor = __esm({
47915
48099
  "src/supervisor.ts"() {
47916
48100
  "use strict";
@@ -47918,10 +48102,13 @@ var init_supervisor = __esm({
47918
48102
  init_dist7();
47919
48103
  init_env2();
47920
48104
  init_auth();
48105
+ init_session_renewal();
48106
+ init_claim();
47921
48107
  init_options();
47922
48108
  init_harness_signal();
47923
48109
  init_process();
47924
48110
  init_pacing();
48111
+ init_wake_clock();
47925
48112
  init_queries();
47926
48113
  init_worktree();
47927
48114
  argv = process.argv.slice(2);
@@ -48079,6 +48266,7 @@ cruo-supervisor: ${error51.message}
48079
48266
  }
48080
48267
  })();
48081
48268
  log = (...parts) => console.log(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB", { hour12: false })}]`, ...parts);
48269
+ sessionRenewal = { renew: refreshContext, log };
48082
48270
  worktreeConfig = null;
48083
48271
  lastRefusal = null;
48084
48272
  CONFIG_DIR_PREFIX = "cruo-sup-";