cruo-agent 0.1.14 → 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 +287 -81
  3. package/dist/index.js +38253 -2877
  4. package/package.json +1 -1
package/dist/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.14
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;
@@ -46535,8 +46628,14 @@ function refusalIn(stdout) {
46535
46628
  }
46536
46629
  return null;
46537
46630
  }
46631
+ function costlessInstant(run) {
46632
+ if (!run.usage || run.durationMs >= 1e4) return false;
46633
+ const tokens = (run.usage.inputTokens ?? 0) + (run.usage.outputTokens ?? 0);
46634
+ const cost = run.usage.costUsd ?? 0;
46635
+ return tokens === 0 && cost === 0;
46636
+ }
46538
46637
  function neverRan(run) {
46539
- return run.refusal !== null || run.stdoutBytes === 0;
46638
+ return run.refusal !== null || run.stdoutBytes === 0 || costlessInstant(run);
46540
46639
  }
46541
46640
  function usageIn(text) {
46542
46641
  const trimmed = text.trimEnd();
@@ -46641,13 +46740,25 @@ var init_harness_signal = __esm({
46641
46740
  // literal string in the 2.x binary. That one word cost 8,239 runs (CRA-89):
46642
46741
  // the refusal went unrecognised, so `neverRan` was false, so the backoff
46643
46742
  // never engaged and every card was charged for an outage.
46743
+ //
46744
+ // The NOUN is a family, not a list to extend one incident at a time. This
46745
+ // pattern has now been widened three times — `usage` (CRA-75), `reached
46746
+ // your` (CRA-89), and `session` (CRA-125, after "You've hit your session
46747
+ // limit · resets 2:30pm" set two cards aside inside sixty seconds). Each
46748
+ // miss costs the same way: the refusal is unrecognised, `neverRan` is false,
46749
+ // and an account outage is charged to whatever cards the agent was holding.
46750
+ // The word `limit` is still required, so a model writing about limits in a
46751
+ // card is not matched.
46644
46752
  [
46645
- /(hit|reached) your (monthly |weekly |daily )?(spend|usage) limit/i,
46753
+ /(hit|reached) your (monthly |weekly |daily |hourly )?(spend|usage|session|message|rate) limit/i,
46646
46754
  "the account's usage limit is reached"
46647
46755
  ],
46648
46756
  // The passive voice of the same thing, which is what the harness prints when
46649
46757
  // it is reporting rather than addressing you.
46650
- [/(usage|spend|usage credit) limit reached/i, "the account's usage limit is reached"],
46758
+ [
46759
+ /(usage|spend|usage credit|session|message|rate) limit reached/i,
46760
+ "the account's usage limit is reached"
46761
+ ],
46651
46762
  // `is` optional: the binary carries BOTH "credit balance is too low" and
46652
46763
  // "credit balance too low", and the pattern only knew the longer one.
46653
46764
  [
@@ -46707,6 +46818,48 @@ var init_pacing = __esm({
46707
46818
  }
46708
46819
  });
46709
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
+
46710
46863
  // src/queries.ts
46711
46864
  function blocksAgentPickup(state) {
46712
46865
  return state.requires_human === true;
@@ -47024,15 +47177,19 @@ async function pollMentions(ctx, identity) {
47024
47177
  }
47025
47178
  async function claim(ctx, hit) {
47026
47179
  const ttlMs = options.harnessTimeoutMs + 3e4;
47027
- const { data, error: error51 } = await ctx.client.schema("public").rpc("claim_issue", {
47028
- issue: hit.issue.id,
47029
- ttl_ms: ttlMs
47030
- });
47031
- if (error51) {
47032
- log(` could not claim ${hit.ref}: ${error51.message} \u2014 leaving it`);
47033
- 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`);
47034
47191
  }
47035
- return data === true;
47192
+ return outcome;
47036
47193
  }
47037
47194
  async function claimedElsewhere(ctx, hits) {
47038
47195
  if (hits.length === 0) return /* @__PURE__ */ new Set();
@@ -47044,12 +47201,20 @@ async function claimedElsewhere(ctx, hits) {
47044
47201
  return new Set((data ?? []).map((r) => r.issue_id));
47045
47202
  }
47046
47203
  async function release(ctx, hit) {
47047
- 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
+ );
47048
47209
  if (error51) log(` could not release ${hit.ref}: ${error51.message}`);
47049
47210
  }
47050
47211
  async function markRead(ctx, notificationIds) {
47051
47212
  if (notificationIds.length === 0) return;
47052
- 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
+ );
47053
47218
  if (error51) log(` could not mark ${notificationIds.length} mention(s) read: ${error51.message}`);
47054
47219
  }
47055
47220
  async function writeMcpConfig() {
@@ -47333,14 +47498,29 @@ async function invokeHarness(ctx, identity, hit, worktree) {
47333
47498
  });
47334
47499
  child.stderr?.on("data", (c) => process.stderr.write(c));
47335
47500
  let killed = false;
47336
- const timer = setTimeout(() => {
47337
- killed = true;
47338
- log(` harness exceeded ${options.harnessTimeoutMs / 1e3}s \u2014 killing it`);
47339
- child.kill("SIGTERM");
47340
- setTimeout(() => child.kill("SIGKILL"), 1e4).unref();
47341
- }, 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?.();
47342
47522
  child.on("error", (e) => {
47343
- clearTimeout(timer);
47523
+ clearInterval(wakeTimer);
47344
47524
  log(` harness failed to start: ${e.message}`);
47345
47525
  resolve2({
47346
47526
  code: 127,
@@ -47349,6 +47529,7 @@ async function invokeHarness(ctx, identity, hit, worktree) {
47349
47529
  durationMs: Date.now() - startedAtMs,
47350
47530
  usage: null,
47351
47531
  killed,
47532
+ sawSleep: wakeClock.sawSleep,
47352
47533
  stdoutHead: headFor(head2),
47353
47534
  openingContext: null,
47354
47535
  systemPromptChars: systemPromptText.length,
@@ -47356,7 +47537,7 @@ async function invokeHarness(ctx, identity, hit, worktree) {
47356
47537
  });
47357
47538
  });
47358
47539
  child.on("close", (code) => {
47359
- clearTimeout(timer);
47540
+ clearInterval(wakeTimer);
47360
47541
  if (streaming && pending.trim() !== "") {
47361
47542
  tail = pending;
47362
47543
  if (openingContext === null) openingContext = turnUsageIn(pending);
@@ -47372,6 +47553,7 @@ async function invokeHarness(ctx, identity, hit, worktree) {
47372
47553
  durationMs: Date.now() - startedAtMs,
47373
47554
  usage: usageIn(tail),
47374
47555
  killed,
47556
+ sawSleep: wakeClock.sawSleep,
47375
47557
  stdoutHead: headFor(head2),
47376
47558
  openingContext,
47377
47559
  systemPromptChars: systemPromptText.length,
@@ -47396,25 +47578,33 @@ async function loadAttempts(ctx, hits) {
47396
47578
  }
47397
47579
  async function recordAttempt(ctx, hit, outcome, previous) {
47398
47580
  const attempts = previous + 1;
47399
- const { error: error51 } = await ctx.client.from("issue_attempts").upsert(
47400
- {
47401
- issue_id: hit.issue.id,
47402
- agent_id: ctx.userId,
47403
- reason: hit.reason,
47404
- attempts,
47405
- last_tried_at: (/* @__PURE__ */ new Date()).toISOString(),
47406
- last_outcome: outcome,
47407
- // Stamped at the moment the budget runs out, so the board can say when
47408
- // the automation gave up rather than only that it did.
47409
- set_aside_at: attempts >= options.maxAttempts ? (/* @__PURE__ */ new Date()).toISOString() : null
47410
- },
47411
- { 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
47412
47598
  );
47413
47599
  if (error51) log(` could not record the attempt on ${hit.ref}: ${error51.message}`);
47414
47600
  return attempts;
47415
47601
  }
47416
47602
  async function clearAttempts(ctx, hit) {
47417
- 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
+ );
47418
47608
  if (error51) log(` could not clear attempts on ${hit.ref}: ${error51.message}`);
47419
47609
  }
47420
47610
  async function repliedSince(ctx, hit, since2) {
@@ -47446,20 +47636,24 @@ async function publish(ctx, hit, worktree) {
47446
47636
  }
47447
47637
  }
47448
47638
  async function beat(ctx, fields) {
47449
- const { error: error51 } = await ctx.client.from("agent_heartbeats").upsert(
47450
- {
47451
- user_id: ctx.userId,
47452
- workspace_id: ctx.workspaceId,
47453
- last_seen_at: (/* @__PURE__ */ new Date()).toISOString(),
47454
- holding: fields.holding,
47455
- dead_ticks: fields.deadTicks,
47456
- poll_interval_ms: fields.intervalMs,
47457
- // CRA-102. Without this the board cannot tell an agent someone paused
47458
- // from one that is wading through a backlog, nor one stopped on purpose
47459
- // from one whose laptop shut its lid all of them simply went quiet.
47460
- status: fields.status ?? (paused ? "paused" : "running")
47461
- },
47462
- { 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
47463
47657
  );
47464
47658
  if (error51) log(` could not record a heartbeat: ${error51.message}`);
47465
47659
  lastHolding = fields.holding;
@@ -47488,28 +47682,32 @@ function beatWhileBusy(ctx, deadTicks) {
47488
47682
  return () => clearInterval(timer);
47489
47683
  }
47490
47684
  async function recordRun(ctx, hit, run, outcome) {
47491
- const { error: error51 } = await ctx.client.from("harness_runs").insert({
47492
- agent_id: ctx.userId,
47493
- workspace_id: ctx.workspaceId,
47494
- issue_id: hit.issue.id,
47495
- reason: hit.reason,
47496
- duration_ms: run.durationMs,
47497
- exit_code: run.code,
47498
- stdout_bytes: run.stdoutBytes,
47499
- stdout_head: run.stdoutHead,
47500
- outcome,
47501
- model: run.usage?.model ?? null,
47502
- input_tokens: run.usage?.inputTokens ?? null,
47503
- output_tokens: run.usage?.outputTokens ?? null,
47504
- cache_read_tokens: run.usage?.cacheReadTokens ?? null,
47505
- cache_write_tokens: run.usage?.cacheWriteTokens ?? null,
47506
- cost_usd: run.usage?.costUsd ?? null,
47507
- opening_context_input_tokens: run.openingContext?.inputTokens ?? null,
47508
- opening_context_cache_read_tokens: run.openingContext?.cacheReadTokens ?? null,
47509
- opening_context_cache_write_tokens: run.openingContext?.cacheWriteTokens ?? null,
47510
- system_prompt_chars: run.systemPromptChars,
47511
- user_prompt_chars: run.userPromptChars
47512
- });
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
+ );
47513
47711
  if (error51 && !warnedAboutSpendTable) {
47514
47712
  warnedAboutSpendTable = true;
47515
47713
  log(` (not recording run costs: ${error51.message})`);
@@ -47579,8 +47777,9 @@ async function tick(ctx, identity, deadTicks, allowance) {
47579
47777
  invoked += 1;
47580
47778
  const before = { status: hit.issue.status_id, assignee: hit.issue.assignee_id };
47581
47779
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
47582
- if (!await claim(ctx, hit)) {
47583
- 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`);
47584
47783
  continue;
47585
47784
  }
47586
47785
  let worktree = null;
@@ -47661,6 +47860,9 @@ async function tick(ctx, identity, deadTicks, allowance) {
47661
47860
  } else {
47662
47861
  log(` ${hit.ref} handed on (harness exit ${code})`);
47663
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`);
47664
47866
  } else {
47665
47867
  const what = hit.reason === "mention" ? "no reply written" : "unchanged";
47666
47868
  const note = salvaged ? `${what} (exit ${code}); cut short, work saved as ${salvaged.slice(0, 8)}` : `${what} (exit ${code})`;
@@ -47892,7 +48094,7 @@ to catch: a second supervisor nothing could see or stop.
47892
48094
  if (stopping) return;
47893
48095
  }
47894
48096
  }
47895
- 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;
47896
48098
  var init_supervisor = __esm({
47897
48099
  "src/supervisor.ts"() {
47898
48100
  "use strict";
@@ -47900,10 +48102,13 @@ var init_supervisor = __esm({
47900
48102
  init_dist7();
47901
48103
  init_env2();
47902
48104
  init_auth();
48105
+ init_session_renewal();
48106
+ init_claim();
47903
48107
  init_options();
47904
48108
  init_harness_signal();
47905
48109
  init_process();
47906
48110
  init_pacing();
48111
+ init_wake_clock();
47907
48112
  init_queries();
47908
48113
  init_worktree();
47909
48114
  argv = process.argv.slice(2);
@@ -48061,6 +48266,7 @@ cruo-supervisor: ${error51.message}
48061
48266
  }
48062
48267
  })();
48063
48268
  log = (...parts) => console.log(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB", { hour12: false })}]`, ...parts);
48269
+ sessionRenewal = { renew: refreshContext, log };
48064
48270
  worktreeConfig = null;
48065
48271
  lastRefusal = null;
48066
48272
  CONFIG_DIR_PREFIX = "cruo-sup-";