@rallycry/conveyor-agent 10.8.1 → 10.9.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.
@@ -951,6 +951,19 @@ var AgentConnection = class _AgentConnection {
951
951
  }).catch(() => {
952
952
  });
953
953
  }
954
+ /**
955
+ * The session's key hit a hard usage cap — ask the server to stamp it
956
+ * limited and hand back the best remaining key's credential env (or a
957
+ * requeue confirmation when none is left). Awaited: the caller swaps
958
+ * credentials and resumes on success, so it needs the real response.
959
+ */
960
+ async cycleCodingAgentKey(rateLimitType, resetsAt) {
961
+ return await this.call("cycleCodingAgentKey", {
962
+ sessionId: this.config.sessionId,
963
+ rateLimitType,
964
+ ...resetsAt ? { resetsAt } : {}
965
+ });
966
+ }
954
967
  // ── Question handling ──────────────────────────────────────────────
955
968
  async askUserQuestion(questions) {
956
969
  const questionText = questions.map(
@@ -2175,6 +2188,11 @@ var SubmitCodeReviewResultRequestSchema = z.object({
2175
2188
  approved: z.boolean(),
2176
2189
  content: z.string()
2177
2190
  });
2191
+ var CycleCodingAgentKeyRequestSchema = z.object({
2192
+ sessionId: z.string(),
2193
+ rateLimitType: z.string(),
2194
+ resetsAt: z.string().optional()
2195
+ });
2178
2196
  var StartChildCloudBuildRequestSchema = z.object({
2179
2197
  sessionId: z.string(),
2180
2198
  childTaskId: z.string()
@@ -2726,7 +2744,7 @@ var ModeController = class {
2726
2744
  }
2727
2745
  get isBuildCapable() {
2728
2746
  const m = this.effectiveMode;
2729
- return m === "building" || m === "review" || m === "auto" && this._hasExitedPlanMode;
2747
+ return m === "building" || m === "review" || m === "chat" || m === "auto" && this._hasExitedPlanMode;
2730
2748
  }
2731
2749
  /**
2732
2750
  * Apply authoritative mode from the server's task context.
@@ -3375,6 +3393,36 @@ var JsonlTailer = class {
3375
3393
  }
3376
3394
  };
3377
3395
 
3396
+ // src/harness/pty/limit-banner.ts
3397
+ var BANNER_RE = /you'?(?:ve| have) (?:hit|reached) your ([\w-]+ )?(?:usage )?limit/i;
3398
+ var RESET_RE = /resets?\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)\s*\(UTC\)/i;
3399
+ function nextUtcOccurrenceSeconds(hour12, minute, meridiem, nowMs) {
3400
+ let hour = hour12 % 12;
3401
+ if (meridiem.toLowerCase() === "pm") hour += 12;
3402
+ const candidate = new Date(nowMs);
3403
+ candidate.setUTCHours(hour, minute, 0, 0);
3404
+ if (candidate.getTime() <= nowMs) candidate.setUTCDate(candidate.getUTCDate() + 1);
3405
+ return Math.floor(candidate.getTime() / 1e3);
3406
+ }
3407
+ function matchUsageLimitBanner(text, now = Date.now()) {
3408
+ const banner = BANNER_RE.exec(text);
3409
+ if (!banner) return null;
3410
+ const qualifier = banner[1]?.trim().toLowerCase();
3411
+ const rateLimitType = qualifier === "weekly" ? "seven_day" : "five_hour";
3412
+ const reset = RESET_RE.exec(text);
3413
+ return {
3414
+ rateLimitType,
3415
+ ...reset ? {
3416
+ resetsAtEpochSeconds: nextUtcOccurrenceSeconds(
3417
+ Number(reset[1]),
3418
+ reset[2] ? Number(reset[2]) : 0,
3419
+ reset[3],
3420
+ now
3421
+ )
3422
+ } : {}
3423
+ };
3424
+ }
3425
+
3378
3426
  // src/harness/pty/chat-record-mapper.ts
3379
3427
  var TEXT_MAX = 16e3;
3380
3428
  var TOOL_INPUT_MAX = 1900;
@@ -4095,6 +4143,14 @@ async function readRaw(path4) {
4095
4143
  return null;
4096
4144
  }
4097
4145
  }
4146
+ async function readCredentialsIdentity() {
4147
+ const parsed = parseClaudeAiOauth(await readRaw(claudeCredentialsPath()));
4148
+ if (!parsed) return null;
4149
+ return {
4150
+ accessToken: typeof parsed.accessToken === "string" ? parsed.accessToken : null,
4151
+ hasRefreshToken: typeof parsed.refreshToken === "string" && parsed.refreshToken.length > 0
4152
+ };
4153
+ }
4098
4154
  var READ_BACK_DELAYS_MS = [250, 500, 1e3, 2e3];
4099
4155
  var defaultSleep = (ms) => new Promise((resolve) => {
4100
4156
  setTimeout(resolve, ms);
@@ -4563,6 +4619,7 @@ var PtySession = class {
4563
4619
  pty = null;
4564
4620
  tempDir = "";
4565
4621
  sawResult = false;
4622
+ limitBannerReported = false;
4566
4623
  // Rolling tail of raw PTY output, retained only to enrich the error when the
4567
4624
  // CLI exits before emitting a result (the bytes are otherwise relayed to S5
4568
4625
  // and never become events). Trimmed to MAX_DIAGNOSTIC_OUTPUT on every write.
@@ -5093,6 +5150,7 @@ var PtySession = class {
5093
5150
  this.disarmPlanDialogAutoAccept();
5094
5151
  }
5095
5152
  if (this.pendingSubmitNudge) this.disarmSubmitNudge();
5153
+ this.synthesizeRateLimitFromBanner(event);
5096
5154
  this.pushEvent(event);
5097
5155
  if (event.type === "result") {
5098
5156
  this.sawResult = true;
@@ -5100,6 +5158,35 @@ var PtySession = class {
5100
5158
  this.endTurn(true);
5101
5159
  }
5102
5160
  }
5161
+ /**
5162
+ * The interactive CLI reports a hard usage cap only as a conversation banner
5163
+ * ("You've hit your weekly limit · resets 7am (UTC)") — it never writes a
5164
+ * structured rate_limit_event to the transcript. Recognize the banner in
5165
+ * assistant/result text and push the same harness-level event the SDK
5166
+ * harness emits, so cap handling (key cycle → pause) is one code path.
5167
+ * Once per session: the CLI repeats the banner on every rejected turn.
5168
+ */
5169
+ synthesizeRateLimitFromBanner(event) {
5170
+ if (this.limitBannerReported) return;
5171
+ let text;
5172
+ if (event.type === "assistant") {
5173
+ text = event.message.content.map((block) => block.text ?? "").filter(Boolean).join("\n");
5174
+ } else if (event.type === "result" && event.subtype === "success") {
5175
+ text = event.result;
5176
+ }
5177
+ if (!text) return;
5178
+ const match = matchUsageLimitBanner(text);
5179
+ if (!match) return;
5180
+ this.limitBannerReported = true;
5181
+ this.pushEvent({
5182
+ type: "rate_limit_event",
5183
+ rate_limit_info: {
5184
+ status: "rejected",
5185
+ rateLimitType: match.rateLimitType,
5186
+ ...match.resetsAtEpochSeconds === void 0 ? {} : { resetsAt: match.resetsAtEpochSeconds }
5187
+ }
5188
+ });
5189
+ }
5103
5190
  async finalizeOnExit(exitCode) {
5104
5191
  this.coalescer?.flush();
5105
5192
  this.exited = true;
@@ -6104,10 +6191,30 @@ function buildModePrompt(agentMode, context, runnerMode) {
6104
6191
  return buildReviewPrompt(context);
6105
6192
  case "auto":
6106
6193
  return buildAutoPrompt(context, runnerMode);
6194
+ case "chat":
6195
+ return buildChatPrompt();
6107
6196
  default:
6108
6197
  return null;
6109
6198
  }
6110
6199
  }
6200
+ function buildChatPrompt() {
6201
+ return [
6202
+ `
6203
+ ## Mode: Chat`,
6204
+ `You are in Chat mode \u2014 a conversational assistant working directly with the user on this card.`,
6205
+ `- Respond conversationally to the user in chat. Ask clarifying questions when useful; this is a back-and-forth, not an autonomous build.`,
6206
+ `- You have full read/write access to the workspace and can run non-destructive shell commands, so you CAN create files (notes, scripts, docs, data, diagrams, etc.) when they help the user.`,
6207
+ ``,
6208
+ `### Deliverables \u2014 attach, do NOT open a PR`,
6209
+ `- This card has NO pull-request workflow. Do NOT run \`git push\`, do NOT open a PR, and do NOT rely on branch commits to deliver work \u2014 those operations are blocked.`,
6210
+ `- When you create a file the user should keep, attach it to the card with the \`upload_attachment\` tool so it shows up on the card. Mention in chat what you attached.`,
6211
+ ``,
6212
+ `### Finishing the conversation`,
6213
+ `- When the user indicates they are done (or explicitly asks to wrap up / close the card), call \`force_update_task_status\` with status \`"Complete"\` to move the card InProgress \u2192 Done. There is no review or PR step.`,
6214
+ `- If the user is still engaged, keep the card InProgress and keep helping \u2014 only complete it once the interaction has concluded.`,
6215
+ `- Do not complete the card while you still owe the user a response or an attachment.`
6216
+ ].join("\n");
6217
+ }
6111
6218
  function buildReviewPrompt(context) {
6112
6219
  const parts = [
6113
6220
  `
@@ -7980,16 +8087,20 @@ function buildConveyorTools(connection, config, context, agentMode) {
7980
8087
  const effectiveMode = agentMode ?? context?.agentMode ?? void 0;
7981
8088
  const commonTools = buildCommonTools(connection, config);
7982
8089
  const modeTools = getModeTools(effectiveMode, connection, config, context);
7983
- const discoveryTools = effectiveMode === "discovery" || effectiveMode === "auto" || effectiveMode === "building" ? buildDiscoveryTools(connection) : [];
8090
+ const discoveryTools = effectiveMode === "discovery" || effectiveMode === "auto" || effectiveMode === "building" || effectiveMode === "chat" ? buildDiscoveryTools(connection) : [];
7984
8091
  const codeReviewTools = effectiveMode === "review" ? buildCodeReviewTools(connection) : [];
7985
8092
  const emergencyTools = [buildForceUpdateTaskStatusTool(connection)];
7986
- return withAlwaysLoad([
8093
+ const tools = withAlwaysLoad([
7987
8094
  ...commonTools,
7988
8095
  ...modeTools,
7989
8096
  ...discoveryTools,
7990
8097
  ...codeReviewTools,
7991
8098
  ...emergencyTools
7992
8099
  ]);
8100
+ if (effectiveMode === "chat") {
8101
+ return tools.filter((tool2) => tool2.name !== "create_pull_request");
8102
+ }
8103
+ return tools;
7993
8104
  }
7994
8105
  function createConveyorMcpServer(harness, connection, config, context, agentMode) {
7995
8106
  return harness.createMcpServer({
@@ -8411,6 +8522,13 @@ async function processResultCase(event, host, context, startTime, state) {
8411
8522
  if (info.staleSession) state.staleSession = true;
8412
8523
  if (info.authError) state.authError = true;
8413
8524
  }
8525
+ function processRateLimitCase(event, host, state) {
8526
+ const resetsAt = handleRateLimitEvent(event, host);
8527
+ if (resetsAt) state.rateLimitResetsAt = resetsAt;
8528
+ if (event.rate_limit_info.status === "rejected") {
8529
+ state.rateLimitRejectedType = event.rate_limit_info.rateLimitType ?? "unknown";
8530
+ }
8531
+ }
8414
8532
  async function processEvents(events, context, host) {
8415
8533
  const startTime = Date.now();
8416
8534
  let lastStatusEmit = Date.now();
@@ -8422,6 +8540,7 @@ async function processEvents(events, context, host) {
8422
8540
  sawApiError: false,
8423
8541
  resultSummary: void 0,
8424
8542
  rateLimitResetsAt: void 0,
8543
+ rateLimitRejectedType: void 0,
8425
8544
  staleSession: void 0,
8426
8545
  authError: void 0,
8427
8546
  lastAssistantUsage: void 0,
@@ -8451,11 +8570,9 @@ async function processEvents(events, context, host) {
8451
8570
  case "result":
8452
8571
  await processResultCase(event, host, context, startTime, state);
8453
8572
  break;
8454
- case "rate_limit_event": {
8455
- const resetsAt = handleRateLimitEvent(event, host);
8456
- if (resetsAt) state.rateLimitResetsAt = resetsAt;
8573
+ case "rate_limit_event":
8574
+ processRateLimitCase(event, host, state);
8457
8575
  break;
8458
- }
8459
8576
  case "tool_progress":
8460
8577
  handleToolProgressEvent(event, host);
8461
8578
  break;
@@ -8467,11 +8584,38 @@ async function processEvents(events, context, host) {
8467
8584
  retriable: state.retriable || state.sawApiError,
8468
8585
  resultSummary: state.resultSummary,
8469
8586
  rateLimitResetsAt: state.rateLimitResetsAt,
8587
+ ...state.rateLimitRejectedType && { rateLimitRejectedType: state.rateLimitRejectedType },
8470
8588
  ...state.staleSession && { staleSession: state.staleSession },
8471
8589
  ...state.authError && { authError: state.authError }
8472
8590
  };
8473
8591
  }
8474
8592
 
8593
+ // src/execution/key-cycle.ts
8594
+ var FIVE_HOURS_MS = 5 * 60 * 60 * 1e3;
8595
+ var TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1e3;
8596
+ function applyCycledKeyEnv(envVars, env = process.env) {
8597
+ for (const [key, value] of Object.entries(envVars)) {
8598
+ env[key] = value;
8599
+ }
8600
+ if (envVars.CLAUDE_CODE_OAUTH_TOKEN) {
8601
+ delete env.ANTHROPIC_API_KEY;
8602
+ if (!envVars.CONVEYOR_AGENT_KEY) delete env.CONVEYOR_AGENT_KEY;
8603
+ } else if (envVars.CONVEYOR_AGENT_KEY || envVars.CONVEYOR_OPENCODE_OAUTH) {
8604
+ delete env.CLAUDE_CODE_OAUTH_TOKEN;
8605
+ }
8606
+ }
8607
+ async function syncCredentialsAfterCycle(env = process.env) {
8608
+ if (env.CLAUDE_CODE_OAUTH_TOKEN) {
8609
+ await ensureClaudeCredentials(env);
8610
+ } else {
8611
+ await removeConveyorCredentials();
8612
+ }
8613
+ }
8614
+ function fallbackResetIso(rateLimitType, now = Date.now()) {
8615
+ const weekly = /weekly|seven_day/i.test(rateLimitType);
8616
+ return new Date(now + (weekly ? TWENTY_FOUR_HOURS_MS : FIVE_HOURS_MS)).toISOString();
8617
+ }
8618
+
8475
8619
  // src/execution/task-property-utils.ts
8476
8620
  function collectMissingProps(taskProps) {
8477
8621
  const missing = [];
@@ -8541,6 +8685,19 @@ function handleBuildingToolAccess(toolName, input) {
8541
8685
  function handleReviewToolAccess(toolName, input) {
8542
8686
  return handleBuildingToolAccess(toolName, input);
8543
8687
  }
8688
+ var CHAT_BLOCKED_BASH = /\bgit\s+push\b|\bgh\s+pr\b|\bhub\s+pull-request\b/;
8689
+ function handleChatToolAccess(toolName, input) {
8690
+ if (toolName === "Bash") {
8691
+ const cmd = String(input.command ?? "");
8692
+ if (CHAT_BLOCKED_BASH.test(cmd)) {
8693
+ return {
8694
+ behavior: "deny",
8695
+ message: "Chat mode does not open pull requests. Create files locally and attach them to the card with upload_attachment instead of pushing a branch or opening a PR."
8696
+ };
8697
+ }
8698
+ }
8699
+ return handleBuildingToolAccess(toolName, input);
8700
+ }
8544
8701
  function handleAutoToolAccess(toolName, input, hasExitedPlanMode, isParentTask) {
8545
8702
  if (hasExitedPlanMode) {
8546
8703
  return isParentTask ? handleReviewToolAccess(toolName, input) : handleBuildingToolAccess(toolName, input);
@@ -8675,6 +8832,8 @@ function resolveToolAccess(host, toolName, input) {
8675
8832
  return handleReviewToolAccess(toolName, input);
8676
8833
  case "auto":
8677
8834
  return handleAutoToolAccess(toolName, input, host.hasExitedPlanMode, host.isParentTask);
8835
+ case "chat":
8836
+ return handleChatToolAccess(toolName, input);
8678
8837
  default:
8679
8838
  return { behavior: "allow", updatedInput: input };
8680
8839
  }
@@ -8694,6 +8853,12 @@ function buildCanUseTool(host) {
8694
8853
  if (toolName === "AskUserQuestion") {
8695
8854
  return await handleAskUserQuestion(host, input);
8696
8855
  }
8856
+ if (host.agentMode === "chat" && /(^|__)create_pull_request$/.test(toolName)) {
8857
+ return {
8858
+ behavior: "deny",
8859
+ message: 'Chat mode does not open pull requests. When the conversation is complete, mark the card done with force_update_task_status("Complete").'
8860
+ };
8861
+ }
8697
8862
  const result = resolveToolAccess(host, toolName, input);
8698
8863
  if (result.behavior === "deny") {
8699
8864
  consecutiveDenials++;
@@ -9294,6 +9459,48 @@ function handleRateLimitPause(host, rateLimitResetsAt) {
9294
9459
  `Rate limited. The task will be automatically re-queued and resume after ${new Date(rateLimitResetsAt).toLocaleString()}.`
9295
9460
  );
9296
9461
  }
9462
+ var MAX_KEY_CYCLES = 5;
9463
+ async function handleUsageCapRejection(context, host, options, rateLimitType, resetsAt) {
9464
+ const pauseAt = resetsAt ?? fallbackResetIso(rateLimitType);
9465
+ if (host.keyCycleCount >= MAX_KEY_CYCLES) {
9466
+ handleRateLimitPause(host, pauseAt);
9467
+ return;
9468
+ }
9469
+ host.keyCycleCount += 1;
9470
+ let response;
9471
+ try {
9472
+ response = await host.connection.cycleCodingAgentKey(rateLimitType, resetsAt);
9473
+ } catch (error) {
9474
+ host.connection.postChatMessage(
9475
+ `Usage cap hit and key cycling failed (${getErrorMessage(error)}) \u2014 pausing until ${new Date(pauseAt).toLocaleString()}.`
9476
+ );
9477
+ handleRateLimitPause(host, pauseAt);
9478
+ return;
9479
+ }
9480
+ if (!response.cycled) {
9481
+ handleRateLimitPause(host, response.resetsAt);
9482
+ return;
9483
+ }
9484
+ applyCycledKeyEnv(response.envVars);
9485
+ await syncCredentialsAfterCycle();
9486
+ await host.harness.dispose?.();
9487
+ host.connection.postChatMessage(
9488
+ `Usage cap hit \u2014 switched to key **${response.label}** and resuming.`
9489
+ );
9490
+ context.claudeSessionId = null;
9491
+ host.connection.storeSessionId("");
9492
+ const freshPrompt = buildMultimodalPrompt(
9493
+ await buildInitialPrompt(host.config.mode, context, host.isAuto, host.agentMode),
9494
+ context,
9495
+ host.harnessKind === "pty"
9496
+ );
9497
+ const freshQuery = host.harness.executeQuery({
9498
+ prompt: host.createInputStream(freshPrompt),
9499
+ options: { ...options, sessionId: void 0 },
9500
+ resume: void 0
9501
+ });
9502
+ return runWithRetry(freshQuery, context, host, options);
9503
+ }
9297
9504
  function handleRetryError(error, context, host, options, prevImageError) {
9298
9505
  if (isStaleOrExitedSession(error, context) && context.claudeSessionId) {
9299
9506
  return handleStaleSession(context, host, options);
@@ -9306,9 +9513,17 @@ function handleRetryError(error, context, host, options, prevImageError) {
9306
9513
  }
9307
9514
  function handleProcessResult(result, context, host, options) {
9308
9515
  if (result.modeRestart || host.isStopped()) return { action: "return" };
9309
- if (result.rateLimitResetsAt) {
9310
- handleRateLimitPause(host, result.rateLimitResetsAt);
9311
- return { action: "return" };
9516
+ if (result.rateLimitRejectedType || result.rateLimitResetsAt) {
9517
+ return {
9518
+ action: "return_promise",
9519
+ promise: handleUsageCapRejection(
9520
+ context,
9521
+ host,
9522
+ options,
9523
+ result.rateLimitRejectedType ?? "unknown",
9524
+ result.rateLimitResetsAt
9525
+ )
9526
+ };
9312
9527
  }
9313
9528
  if (result.staleSession && context.claudeSessionId) {
9314
9529
  return { action: "return_promise", promise: handleStaleSession(context, host, options) };
@@ -9391,6 +9606,7 @@ var QueryBridge = class {
9391
9606
  _discoveryCompleted = false;
9392
9607
  _isParentTask = false;
9393
9608
  _wasRateLimited = false;
9609
+ _keyCycleCount = 0;
9394
9610
  _abortController = null;
9395
9611
  /** Called by SessionRunner when ExitPlanMode triggers a mode transition. */
9396
9612
  onModeTransition;
@@ -9568,6 +9784,12 @@ var QueryBridge = class {
9568
9784
  set wasRateLimited(val) {
9569
9785
  bridge._wasRateLimited = val;
9570
9786
  },
9787
+ get keyCycleCount() {
9788
+ return bridge._keyCycleCount;
9789
+ },
9790
+ set keyCycleCount(val) {
9791
+ bridge._keyCycleCount = val;
9792
+ },
9571
9793
  get activeQuery() {
9572
9794
  return bridge.activeQuery;
9573
9795
  },
@@ -9651,11 +9873,21 @@ function normalizeUsageText(stdout) {
9651
9873
  }
9652
9874
  function parseUsageGauges(stdout) {
9653
9875
  const text = normalizeUsageText(stdout);
9654
- const session = text.match(/Current session[^%\n]*?(\d+(?:\.\d+)?)\s*%(?:\s*used)?/i);
9655
- const weekly = [...text.matchAll(/Current week[^%\n]*?(\d+(?:\.\d+)?)\s*%(?:\s*used)?/gi)];
9876
+ const rows = [
9877
+ ...text.matchAll(
9878
+ /Current (session|week)\s*(\([^)\n]*\))?[^%\n]*?(\d+(?:\.\d+)?)\s*%(?:\s*used)?/gi
9879
+ )
9880
+ ];
9881
+ const gauges = rows.map((m) => ({
9882
+ label: `Current ${m[1].toLowerCase()}${m[2] ? ` ${m[2]}` : ""}`,
9883
+ utilization: Number(m[3]) / 100
9884
+ }));
9885
+ const session = gauges.find((g) => g.label.startsWith("Current session"));
9886
+ const weekly = gauges.filter((g) => g.label.startsWith("Current week"));
9656
9887
  return {
9657
- sessionUsage: session ? Number(session[1]) / 100 : null,
9658
- weeklyUsage: weekly.length ? Math.max(...weekly.map((m) => Number(m[1]))) / 100 : null
9888
+ sessionUsage: session ? session.utilization : null,
9889
+ weeklyUsage: weekly.length ? Math.max(...weekly.map((g) => g.utilization)) : null,
9890
+ gauges
9659
9891
  };
9660
9892
  }
9661
9893
 
@@ -9768,17 +10000,44 @@ async function runUsageProbe(deps = {}) {
9768
10000
 
9769
10001
  // src/execution/usage-sampler.ts
9770
10002
  var logger4 = createServiceLogger("usage-sampler");
9771
- async function sampleKeyUsage(token, probe = () => runUsageProbe(), hasSubscriptionCredentials = () => existsSync3(claudeCredentialsPath())) {
10003
+ function isAttributable(identity, sessionToken) {
10004
+ if (!identity) return { ok: true };
10005
+ if (identity.hasRefreshToken) {
10006
+ return { ok: false, reason: "manual-login-credentials" };
10007
+ }
10008
+ if (sessionToken && identity.accessToken && identity.accessToken !== sessionToken) {
10009
+ return { ok: false, reason: "credentials-token-mismatch" };
10010
+ }
10011
+ return { ok: true };
10012
+ }
10013
+ async function sampleKeyUsage(token, probe = () => runUsageProbe(), hasSubscriptionCredentials = () => existsSync3(claudeCredentialsPath()), readIdentity = readCredentialsIdentity) {
9772
10014
  if (!token && !hasSubscriptionCredentials()) return [];
9773
10015
  try {
10016
+ const attributable = isAttributable(await readIdentity(), token);
10017
+ if (!attributable.ok) {
10018
+ logger4.info("usage sample skipped \u2014 credentials not attributable to this session's key", {
10019
+ reason: attributable.reason
10020
+ });
10021
+ return [];
10022
+ }
9774
10023
  const stdout = await probe();
9775
- const { sessionUsage, weeklyUsage } = parseUsageGauges(stdout);
10024
+ const { sessionUsage, weeklyUsage, gauges } = parseUsageGauges(stdout);
9776
10025
  const samples = [];
9777
10026
  if (sessionUsage !== null) {
9778
- samples.push({ rateLimitType: "five_hour", utilization: sessionUsage, status: "allowed" });
10027
+ samples.push({
10028
+ rateLimitType: "five_hour",
10029
+ utilization: sessionUsage,
10030
+ status: "allowed",
10031
+ gauges
10032
+ });
9779
10033
  }
9780
10034
  if (weeklyUsage !== null) {
9781
- samples.push({ rateLimitType: "seven_day", utilization: weeklyUsage, status: "allowed" });
10035
+ samples.push({
10036
+ rateLimitType: "seven_day",
10037
+ utilization: weeklyUsage,
10038
+ status: "allowed",
10039
+ gauges
10040
+ });
9782
10041
  }
9783
10042
  if (samples.length === 0) {
9784
10043
  logger4.info("usage sample produced no gauges", {
@@ -10173,7 +10432,7 @@ function isHeavyGateActive() {
10173
10432
  }
10174
10433
 
10175
10434
  // src/runner/session-runner.ts
10176
- var AUTO_RUN_MODES = /* @__PURE__ */ new Set(["building", "auto", "review", "discovery"]);
10435
+ var AUTO_RUN_MODES = /* @__PURE__ */ new Set(["building", "auto", "review", "discovery", "chat"]);
10177
10436
  var SessionRunner = class _SessionRunner {
10178
10437
  connection;
10179
10438
  mode;
@@ -10744,7 +11003,8 @@ var SessionRunner = class _SessionRunner {
10744
11003
  type: "rate_limit_update",
10745
11004
  rateLimitType: sample.rateLimitType,
10746
11005
  utilization: sample.utilization,
10747
- status: sample.status
11006
+ status: sample.status,
11007
+ gauges: sample.gauges
10748
11008
  });
10749
11009
  }
10750
11010
  }
@@ -11281,4 +11541,4 @@ export {
11281
11541
  runStartCommand,
11282
11542
  unshallowRepo
11283
11543
  };
11284
- //# sourceMappingURL=chunk-LBMFXTIV.js.map
11544
+ //# sourceMappingURL=chunk-H3OGNJS4.js.map