@rallycry/conveyor-agent 11.0.47 → 11.0.49

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.
@@ -12,6 +12,7 @@ import {
12
12
  buildSpawnArgs,
13
13
  cleanTerminalOutput,
14
14
  ensureSessionTarget,
15
+ flattenTerminalText,
15
16
  inheritedEnv,
16
17
  killPtyWithEscalation,
17
18
  needsRawReadyGate,
@@ -30,7 +31,7 @@ import {
30
31
  spawnOptionsFingerprint,
31
32
  transcriptSize,
32
33
  turnOptionsFrom
33
- } from "./chunk-EKTPDG3G.js";
34
+ } from "./chunk-SXZS2R52.js";
34
35
  import {
35
36
  AGENT_STATUS_REASON_USER_QUESTION,
36
37
  AgentConnection,
@@ -95,9 +96,10 @@ import {
95
96
  runQueryGrafanaLogs,
96
97
  stageAndCommit,
97
98
  statWorkspacePath,
99
+ subProjectScopePaths,
98
100
  updateRemoteToken,
99
101
  verifyGitCredential
100
- } from "./chunk-KXB55NJP.js";
102
+ } from "./chunk-O3MXZHIS.js";
101
103
  import {
102
104
  reportBootMilestone
103
105
  } from "./chunk-VIUZS7V4.js";
@@ -859,6 +861,7 @@ function redact(input) {
859
861
 
860
862
  // src/harness/pty/jsonl-tailer.ts
861
863
  import { open } from "fs/promises";
864
+ import { StringDecoder } from "string_decoder";
862
865
 
863
866
  // src/harness/pty/record-mapper.ts
864
867
  function isRecord(value) {
@@ -994,7 +997,9 @@ function mapTranscriptRecord(raw) {
994
997
  }
995
998
 
996
999
  // src/harness/pty/jsonl-tailer.ts
997
- var POLL_INTERVAL_MS = 25;
1000
+ var ACTIVE_POLL_INTERVAL_MS = 100;
1001
+ var IDLE_POLL_INTERVAL_MS = 1e3;
1002
+ var logger = createServiceLogger("JsonlTailer");
998
1003
  var JsonlTailer = class {
999
1004
  constructor(path, onEvent, onRawRecord, mapRecord2 = mapTranscriptRecord) {
1000
1005
  this.path = path;
@@ -1008,35 +1013,59 @@ var JsonlTailer = class {
1008
1013
  mapRecord;
1009
1014
  offset = 0;
1010
1015
  buffer = "";
1016
+ decoder = new StringDecoder("utf8");
1011
1017
  timer = null;
1012
- chain = Promise.resolve();
1018
+ chain = Promise.resolve(false);
1019
+ started = false;
1020
+ pollIntervalMs = ACTIVE_POLL_INTERVAL_MS;
1021
+ readErrorLogged = false;
1013
1022
  _closed = false;
1014
1023
  /**
1015
1024
  * Begin tailing. Pass `fromOffset` to skip bytes already present when the
1016
1025
  * tail starts (used on resume so the prior session's records aren't replayed).
1017
1026
  */
1018
1027
  start(fromOffset = 0) {
1019
- if (this.timer) return;
1028
+ if (this.started || this._closed) return;
1029
+ this.started = true;
1020
1030
  this.offset = fromOffset;
1021
- this.timer = setInterval(() => {
1022
- if (this._closed) return;
1023
- void this.enqueueRead();
1024
- }, POLL_INTERVAL_MS);
1031
+ this.scheduleRead();
1025
1032
  }
1026
1033
  close() {
1027
1034
  this._closed = true;
1028
1035
  if (this.timer) {
1029
- clearInterval(this.timer);
1036
+ clearTimeout(this.timer);
1030
1037
  this.timer = null;
1031
1038
  }
1032
1039
  }
1033
1040
  /** Read any remaining bytes and flush a trailing line without a newline. */
1034
1041
  async flush() {
1035
- await this.enqueueRead();
1036
- if (this.buffer.length > 0) {
1037
- this.emitLine(this.buffer);
1038
- this.buffer = "";
1039
- }
1042
+ const flushed = this.chain.then(async () => {
1043
+ await this.readOnce();
1044
+ this.buffer += this.decoder.end();
1045
+ if (this.buffer.length > 0) {
1046
+ this.emitLine(this.buffer);
1047
+ this.buffer = "";
1048
+ }
1049
+ return false;
1050
+ });
1051
+ this.chain = flushed;
1052
+ await flushed;
1053
+ }
1054
+ scheduleRead() {
1055
+ if (this._closed) return;
1056
+ this.timer = setTimeout(() => {
1057
+ this.timer = null;
1058
+ void this.enqueueRead().then(
1059
+ (changed) => {
1060
+ this.pollIntervalMs = changed ? ACTIVE_POLL_INTERVAL_MS : Math.min(this.pollIntervalMs * 2, IDLE_POLL_INTERVAL_MS);
1061
+ this.scheduleRead();
1062
+ },
1063
+ (error) => {
1064
+ logger.error("Transcript polling failed", { error: String(error) });
1065
+ this.close();
1066
+ }
1067
+ );
1068
+ }, this.pollIntervalMs);
1040
1069
  }
1041
1070
  enqueueRead() {
1042
1071
  this.chain = this.chain.then(() => this.readOnce());
@@ -1044,19 +1073,42 @@ var JsonlTailer = class {
1044
1073
  }
1045
1074
  async readOnce() {
1046
1075
  let handle = null;
1076
+ let chunk = null;
1047
1077
  try {
1048
1078
  handle = await open(this.path, "r");
1049
1079
  const stats = await handle.stat();
1050
- if (stats.size <= this.offset) return;
1080
+ if (stats.size <= this.offset) {
1081
+ this.readErrorLogged = false;
1082
+ return false;
1083
+ }
1051
1084
  const length = stats.size - this.offset;
1052
1085
  const buf = Buffer.alloc(length);
1053
- await handle.read(buf, 0, length, this.offset);
1054
- this.offset = stats.size;
1055
- this.consume(buf.toString("utf8"));
1056
- } catch {
1086
+ let readBytes = 0;
1087
+ while (readBytes < length) {
1088
+ const { bytesRead } = await handle.read(
1089
+ buf,
1090
+ readBytes,
1091
+ length - readBytes,
1092
+ this.offset + readBytes
1093
+ );
1094
+ if (bytesRead === 0) break;
1095
+ readBytes += bytesRead;
1096
+ }
1097
+ chunk = buf.subarray(0, readBytes);
1098
+ this.readErrorLogged = false;
1099
+ } catch (error) {
1100
+ if (!this.readErrorLogged) {
1101
+ logger.warn("Transcript read failed; retrying", { error: String(error) });
1102
+ this.readErrorLogged = true;
1103
+ }
1104
+ return false;
1057
1105
  } finally {
1058
1106
  if (handle) await handle.close();
1059
1107
  }
1108
+ if (!chunk || chunk.length === 0) return false;
1109
+ this.offset += chunk.length;
1110
+ this.consume(this.decoder.write(chunk));
1111
+ return true;
1060
1112
  }
1061
1113
  consume(chunk) {
1062
1114
  this.buffer += chunk;
@@ -2550,12 +2602,11 @@ function conveyorCredentialsMarkerPath() {
2550
2602
  function tokenFingerprint(accessToken) {
2551
2603
  return createHash("sha256").update(accessToken).digest("hex");
2552
2604
  }
2553
- function buildCredentialsMarker(accessToken, now, previousHash, refreshToken, codingAgentKeyId) {
2605
+ function buildCredentialsMarker(accessToken, now, previousHash, codingAgentKeyId) {
2554
2606
  const accessTokenSha256 = tokenFingerprint(accessToken);
2555
2607
  return JSON.stringify({
2556
2608
  accessTokenSha256,
2557
2609
  ...previousHash && previousHash !== accessTokenSha256 ? { previousAccessTokenSha256: previousHash } : {},
2558
- ...refreshToken ? { refreshTokenSha256: tokenFingerprint(refreshToken) } : {},
2559
2610
  ...codingAgentKeyId ? { codingAgentKeyId } : {},
2560
2611
  writtenAt: now
2561
2612
  });
@@ -2636,9 +2687,6 @@ function parseClaudeOauthEnv(blob) {
2636
2687
  const scopes = Array.isArray(record.scopes) ? record.scopes.filter((scope) => typeof scope === "string" && scope !== "") : [];
2637
2688
  return {
2638
2689
  access: record.access,
2639
- refresh: typeof record.refresh === "string" && record.refresh ? record.refresh : void 0,
2640
- expires: typeof record.expires === "number" ? record.expires : void 0,
2641
- refreshExpires: typeof record.refreshExpires === "number" ? record.refreshExpires : void 0,
2642
2690
  scopes: scopes.length > 0 ? scopes : void 0,
2643
2691
  rateLimitTier: typeof record.rateLimitTier === "string" ? record.rateLimitTier : void 0
2644
2692
  };
@@ -2664,13 +2712,10 @@ function parseClaudeAiOauth(raw) {
2664
2712
  }
2665
2713
  }
2666
2714
  function buildCredentialsFile(material, now) {
2667
- const refresh = material.refresh;
2668
2715
  return JSON.stringify({
2669
2716
  claudeAiOauth: {
2670
2717
  accessToken: material.access,
2671
- ...refresh ? { refreshToken: refresh } : {},
2672
- expiresAt: refresh && material.expires ? material.expires : now + SYNTH_TOKEN_TTL_MS,
2673
- ...material.refreshExpires ? { refreshTokenExpiresAt: material.refreshExpires } : {},
2718
+ expiresAt: now + SYNTH_TOKEN_TTL_MS,
2674
2719
  scopes: material.scopes?.length ? material.scopes : LEGACY_SCOPES,
2675
2720
  ...material.rateLimitTier ? { rateLimitTier: material.rateLimitTier } : {},
2676
2721
  subscriptionType: "max"
@@ -2686,19 +2731,13 @@ function isConveyorOwnedCredentials(existing, markerHashes) {
2686
2731
  if (typeof existing.accessToken !== "string") return false;
2687
2732
  return markerHashes.includes(tokenFingerprint(existing.accessToken));
2688
2733
  }
2689
- function hasStableRefreshOwnership(existing, markerRaw) {
2690
- return typeof existing.refreshToken === "string" && parseCredentialsRefreshMarker(markerRaw) === tokenFingerprint(existing.refreshToken);
2691
- }
2692
2734
  function isCurrentCredential(existing, material, now) {
2693
- if (material.refresh) return existing.accessToken === material.access;
2735
+ if (typeof existing.refreshToken === "string" && existing.refreshToken !== "") return false;
2694
2736
  return existing.accessToken === material.access && typeof existing.expiresAt === "number" && existing.expiresAt > now + REFRESH_SKEW_MS;
2695
2737
  }
2696
- function needsMarkerUpgrade(input, material) {
2738
+ function needsMarkerUpgrade(input) {
2697
2739
  if (!input.codingAgentKeyId) return false;
2698
- if (parseCredentialsMarkerKeyId(input.markerRaw ?? null) !== input.codingAgentKeyId) return true;
2699
- return Boolean(
2700
- material.refresh && parseCredentialsRefreshMarker(input.markerRaw ?? null) !== tokenFingerprint(material.refresh)
2701
- );
2740
+ return parseCredentialsMarkerKeyId(input.markerRaw ?? null) !== input.codingAgentKeyId;
2702
2741
  }
2703
2742
  function ownsExistingCredential(existing, markerRaw, markerHashes) {
2704
2743
  if (isConveyorOwnedCredentials(existing, markerHashes)) return true;
@@ -2706,11 +2745,6 @@ function ownsExistingCredential(existing, markerRaw, markerHashes) {
2706
2745
  const refreshToken = typeof existing.refreshToken === "string" ? existing.refreshToken : null;
2707
2746
  return credentialMarkerMatches(markerRaw, accessToken, refreshToken);
2708
2747
  }
2709
- function cliRefreshedSameKey(input, material, existing, markerRaw) {
2710
- if (!material.refresh || !input.codingAgentKeyId) return false;
2711
- if (parseCredentialsMarkerKeyId(markerRaw) !== input.codingAgentKeyId) return false;
2712
- return hasStableRefreshOwnership(existing, markerRaw) && existing.accessToken !== material.access;
2713
- }
2714
2748
  function planCredentialsWrite(input) {
2715
2749
  if (!input.isCloud) return { action: "skip", reason: "not-cloud" };
2716
2750
  const material = parseClaudeOauthEnv(input.oauthBlob) ?? (input.token ? { access: input.token } : null);
@@ -2722,7 +2756,6 @@ function planCredentialsWrite(input) {
2722
2756
  material.access,
2723
2757
  input.now,
2724
2758
  markerHashes[0] ?? null,
2725
- material.refresh,
2726
2759
  input.codingAgentKeyId
2727
2760
  );
2728
2761
  const existing = parseClaudeAiOauth(input.existingRaw);
@@ -2730,13 +2763,10 @@ function planCredentialsWrite(input) {
2730
2763
  if (!ownsExistingCredential(existing, markerRaw, markerHashes)) {
2731
2764
  return { action: "skip", reason: "foreign-credentials" };
2732
2765
  }
2733
- if (cliRefreshedSameKey(input, material, existing, markerRaw)) {
2734
- return { action: "skip", reason: "current" };
2735
- }
2736
2766
  if (!isCurrentCredential(existing, material, input.now)) {
2737
2767
  return { action: "write", contents, marker };
2738
2768
  }
2739
- return needsMarkerUpgrade(input, material) ? { action: "mark", marker } : { action: "skip", reason: "current" };
2769
+ return needsMarkerUpgrade(input) ? { action: "mark", marker } : { action: "skip", reason: "current" };
2740
2770
  }
2741
2771
 
2742
2772
  // src/harness/pty/credentials.ts
@@ -4319,6 +4349,9 @@ function isMountDisconnectError(err) {
4319
4349
  const lower = message.toLowerCase();
4320
4350
  return MOUNT_DISCONNECT_MESSAGES.some((needle) => lower.includes(needle));
4321
4351
  }
4352
+ function isRequiredHomeDisconnect(err) {
4353
+ return process.env.CONVEYOR_USER_HOME_REQUIRED === "1" && isMountDisconnectError(err);
4354
+ }
4322
4355
  function podLocalConfigHome() {
4323
4356
  return join9(homedir3(), ".claude-local");
4324
4357
  }
@@ -4367,6 +4400,7 @@ async function ensureUsableClaudeConfigHome(cwd, log) {
4367
4400
  return { configHome, fellBack: false };
4368
4401
  } catch (err) {
4369
4402
  if (!isMountDisconnectError(err)) throw err;
4403
+ if (isRequiredHomeDisconnect(err)) throw err;
4370
4404
  const fallback = podLocalConfigHome();
4371
4405
  log?.warn(
4372
4406
  "shared ~/.claude mount is unreachable; falling back to a pod-local config home. Session history will not persist across pods until the mount recovers.",
@@ -4577,7 +4611,7 @@ var PtyHarness = class _PtyHarness {
4577
4611
  async parkedHomeDied(options) {
4578
4612
  if (!this.ownsClaudeConfigHome) return false;
4579
4613
  try {
4580
- const { fellBack } = await ensureUsableClaudeConfigHome(options.cwd, _PtyHarness.log);
4614
+ const { fellBack } = await this.ensureConfigHome(options);
4581
4615
  if (!fellBack) return false;
4582
4616
  _PtyHarness.log.warn(
4583
4617
  "shared ~/.claude mount died while the CLI was parked \u2014 respawning on the pod-local config home instead of reusing a process bound to the dead mount"
@@ -4587,7 +4621,15 @@ var PtyHarness = class _PtyHarness {
4587
4621
  _PtyHarness.log.warn("parked-session config-home probe failed", {
4588
4622
  error: err instanceof Error ? err.message : String(err)
4589
4623
  });
4590
- return false;
4624
+ return isRequiredHomeDisconnect(err);
4625
+ }
4626
+ }
4627
+ async ensureConfigHome(options) {
4628
+ try {
4629
+ return await ensureUsableClaudeConfigHome(options.cwd, _PtyHarness.log);
4630
+ } catch (err) {
4631
+ if (isRequiredHomeDisconnect(err)) this.escalateToPodRecycle(true);
4632
+ throw err;
4591
4633
  }
4592
4634
  }
4593
4635
  /**
@@ -4599,7 +4641,7 @@ var PtyHarness = class _PtyHarness {
4599
4641
  const session = new PtySession(prompt, options, want, this.bridge, this.adapter);
4600
4642
  onAllocated?.(session);
4601
4643
  if (this.ownsClaudeConfigHome) {
4602
- await ensureUsableClaudeConfigHome(options.cwd, _PtyHarness.log);
4644
+ await this.ensureConfigHome(options);
4603
4645
  }
4604
4646
  await this.adapter.prepareEnvironment({ cwd: options.cwd });
4605
4647
  if (this.ownsClaudeConfigHome) {
@@ -4671,15 +4713,13 @@ var PtyHarness = class _PtyHarness {
4671
4713
  *
4672
4714
  * Fire-and-forget: the escalation must not gate ending the failed turn.
4673
4715
  */
4674
- escalateToPodRecycle() {
4716
+ escalateToPodRecycle(confirmedRequiredMountFailure = false) {
4675
4717
  if (this.recycleRequested || !this.bridge?.requestPodRecycle) return;
4676
- if (!isConfigHomeFallbackActive()) return;
4718
+ if (!confirmedRequiredMountFailure && !isConfigHomeFallbackActive()) return;
4677
4719
  this.recycleRequested = true;
4678
- _PtyHarness.log.error(
4679
- "Prompt delivery failed with the shared ~/.claude mount dead \u2014 requesting a pod recycle"
4680
- );
4720
+ _PtyHarness.log.error("Shared ~/.claude mount is unavailable \u2014 requesting a pod recycle");
4681
4721
  this.bridge.requestPodRecycle(
4682
- "The shared ~/.claude mount died and the Claude CLI will not accept a prompt on this pod. Only a new pod remounts it."
4722
+ "The shared ~/.claude mount died and the Claude CLI cannot safely continue on this pod. Only a new pod remounts it."
4683
4723
  );
4684
4724
  }
4685
4725
  /**
@@ -4846,7 +4886,7 @@ function findOnPath(binary, env = process.env) {
4846
4886
  import { promises as fs } from "fs";
4847
4887
  import { dirname as dirname2, join as join11 } from "path";
4848
4888
  import { homedir as homedir4 } from "os";
4849
- var logger = createServiceLogger("opencode-auth");
4889
+ var logger2 = createServiceLogger("opencode-auth");
4850
4890
  var OPENCODE_CODEX_PLUGIN = "opencode-openai-codex-auth@4.4.0";
4851
4891
  var PLUGIN_PACKAGE = "opencode-openai-codex-auth";
4852
4892
  function opencodeAuthPath(env) {
@@ -4893,7 +4933,7 @@ async function ensureAuthEntry(env, seed) {
4893
4933
  const path = opencodeAuthPath(env);
4894
4934
  const store = await readJsonFile(path);
4895
4935
  if (!shouldSeed(store.openai, seed)) {
4896
- logger.info("opencode oauth store is fresher than the seed; leaving it alone");
4936
+ logger2.info("opencode oauth store is fresher than the seed; leaving it alone");
4897
4937
  return;
4898
4938
  }
4899
4939
  store.openai = {
@@ -4903,7 +4943,7 @@ async function ensureAuthEntry(env, seed) {
4903
4943
  expires: seed.expires
4904
4944
  };
4905
4945
  await writeJsonFile(path, store);
4906
- logger.info("seeded opencode oauth store entry");
4946
+ logger2.info("seeded opencode oauth store entry");
4907
4947
  }
4908
4948
  async function ensurePluginConfig(env) {
4909
4949
  const path = opencodeConfigPath(env);
@@ -4916,7 +4956,7 @@ async function ensurePluginConfig(env) {
4916
4956
  const kept = plugins.filter((p) => !isOurs(p));
4917
4957
  config.plugin = [...kept, OPENCODE_CODEX_PLUGIN];
4918
4958
  await writeJsonFile(path, config);
4919
- logger.info("ensured opencode codex-auth plugin in config");
4959
+ logger2.info("ensured opencode codex-auth plugin in config");
4920
4960
  }
4921
4961
  async function seedOpenCodeOauth(env) {
4922
4962
  const seed = parseOauthSeed(env.CONVEYOR_OPENCODE_OAUTH);
@@ -4925,7 +4965,7 @@ async function seedOpenCodeOauth(env) {
4925
4965
  await ensureAuthEntry(env, seed);
4926
4966
  await ensurePluginConfig(env);
4927
4967
  } catch (err) {
4928
- logger.warn(
4968
+ logger2.warn(
4929
4969
  `failed to seed opencode oauth store: ${err instanceof Error ? err.message : String(err)}`
4930
4970
  );
4931
4971
  }
@@ -5538,14 +5578,14 @@ function resolveTuiAdapter(kind = "claude-code") {
5538
5578
 
5539
5579
  // src/harness/pty/stream-server.ts
5540
5580
  import net from "net";
5541
- var logger2 = createServiceLogger("PtyStreamServer");
5581
+ var logger3 = createServiceLogger("PtyStreamServer");
5542
5582
  var RING_MAX_CHARS = 256 * 1024;
5543
5583
  var PtyStreamServer = class {
5544
5584
  constructor(options) {
5545
5585
  this.options = options;
5546
5586
  this.server = net.createServer((socket) => this.handleConnection(socket));
5547
5587
  this.server.on("error", (err) => {
5548
- logger2.warn(`PTY stream server error: ${err.message}`);
5588
+ logger3.warn(`PTY stream server error: ${err.message}`);
5549
5589
  });
5550
5590
  }
5551
5591
  options;
@@ -5586,7 +5626,7 @@ var PtyStreamServer = class {
5586
5626
  return port;
5587
5627
  }
5588
5628
  }
5589
- logger2.warn(`PTY stream server could not bind any port in ${base}..${base + attempts - 1}`);
5629
+ logger3.warn(`PTY stream server could not bind any port in ${base}..${base + attempts - 1}`);
5590
5630
  return null;
5591
5631
  }
5592
5632
  tryListen(port) {
@@ -5707,7 +5747,7 @@ var PtyStreamServer = class {
5707
5747
  };
5708
5748
 
5709
5749
  // src/harness/pty/direct-stream.ts
5710
- var logger3 = createServiceLogger("PtyDirectStream");
5750
+ var logger4 = createServiceLogger("PtyDirectStream");
5711
5751
  var RELAY_COALESCE_MS = 2e3;
5712
5752
  var RELAY_MAX_BUFFER_CHARS = 48 * 1024;
5713
5753
  var DirectStreamController = class {
@@ -5759,7 +5799,7 @@ var DirectStreamController = class {
5759
5799
  });
5760
5800
  void created.listen().then((port) => this.onListening(created, port)).catch((err) => {
5761
5801
  this.starting = false;
5762
- logger3.warn(
5802
+ logger4.warn(
5763
5803
  `PTY stream server failed to start: ${err instanceof Error ? err.message : String(err)}`
5764
5804
  );
5765
5805
  });
@@ -5772,7 +5812,7 @@ var DirectStreamController = class {
5772
5812
  }
5773
5813
  this.server = created;
5774
5814
  this.reporter.reportPtyStream(port);
5775
- logger3.info(`PTY stream server listening on ${port} (session ${this.reporter.sessionId})`);
5815
+ logger4.info(`PTY stream server listening on ${port} (session ${this.reporter.sessionId})`);
5776
5816
  }
5777
5817
  /** Push the min box across both transports to the pty. */
5778
5818
  applyDims() {
@@ -7110,14 +7150,16 @@ ${context.projectDescription}` : "";
7110
7150
  parts.push(`
7111
7151
  ## Project: ${context.projectName}${descLine}`);
7112
7152
  }
7113
- if (context.subProject?.rootPath) {
7153
+ const scopePaths = context.subProject ? subProjectScopePaths(context.subProject) : [];
7154
+ if (context.subProject && scopePaths.length > 0) {
7155
+ const folders = scopePaths.map((path) => `\`${path}\``).join(", ");
7114
7156
  parts.push(
7115
7157
  `
7116
7158
  ## Sub-project scope: ${context.subProject.name}`,
7117
- `This task belongs to the "${context.subProject.name}" sub-project, which owns \`${context.subProject.rootPath}\`.`,
7159
+ `This task belongs to the "${context.subProject.name}" sub-project, which allows changes under ${folders}.`,
7118
7160
  `- Read and reference the whole repository freely.`,
7119
- `- Substantive code changes belong under \`${context.subProject.rootPath}\`. Trivial wiring elsewhere (exports, route registration) is fine when required.`,
7120
- `- If the work genuinely requires substantive changes outside that folder, do NOT make them here \u2014 create a task on the parent project board (as part of a pack with this one) describing the needed change.`
7161
+ `- Substantive code changes belong under ${folders}. Trivial wiring elsewhere (exports, route registration) is fine when required.`,
7162
+ `- If the work genuinely requires substantive changes outside those folders, do NOT make them here \u2014 create a task on the parent project board (as part of a pack with this one) describing the needed change.`
7121
7163
  );
7122
7164
  }
7123
7165
  if (context.description) {
@@ -7498,9 +7540,9 @@ var searchFilterFields = {
7498
7540
  desc: "Only return cards with no assignee (mutually exclusive with assigneeId)"
7499
7541
  })
7500
7542
  ),
7501
- limit: f.optional(f.number({ desc: "Max results to return (default 20)" }))
7543
+ limit: f.optional(f.number({ desc: "Max results to return (default 20, at most 200)" }))
7502
7544
  };
7503
- var searchResultNote = "Every result lists the tags it carries, so you can see why it matched. Results are relevance-ordered: highest priority first then newest; suggestions-only queries rank by upvote score. Returns summaries \u2014 plan omitted, description truncated; use get_task for full details.";
7545
+ var searchResultNote = "Every result lists the tags it carries, so you can see why it matched. Searches every status, Complete and Cancelled included, unless statusFilters narrows it. Returns at most 200 results. Results are relevance-ordered: highest priority first then newest; suggestions-only queries rank by upvote score. Returns summaries \u2014 plan omitted, description truncated; use get_task for full details.";
7504
7546
  var searchTasksContract = defineToolContract({
7505
7547
  name: "search_tasks",
7506
7548
  agent: {
@@ -12437,7 +12479,7 @@ function resolvePlaywrightMcpServer(env = process.env) {
12437
12479
  }
12438
12480
 
12439
12481
  // src/execution/event-handlers.ts
12440
- var logger4 = createServiceLogger("event-handlers");
12482
+ var logger5 = createServiceLogger("event-handlers");
12441
12483
  function safeVoid(promise, context) {
12442
12484
  if (promise && typeof promise.catch === "function") {
12443
12485
  promise.catch((err) => {
@@ -12639,7 +12681,7 @@ async function emitResultEvent(event, host, context, startTime, lastAssistantUsa
12639
12681
  }
12640
12682
  function handleRateLimitEvent(event, host) {
12641
12683
  const { rate_limit_info } = event;
12642
- logger4.info("Rate limit event received", { rate_limit_info });
12684
+ logger5.info("Rate limit event received", { rate_limit_info });
12643
12685
  const status = rate_limit_info.status;
12644
12686
  const resetsAt = epochSecondsToISO(rate_limit_info.resetsAt);
12645
12687
  const utilization = rate_limit_info.utilization ?? (status === "rejected" ? 1 : void 0);
@@ -13418,7 +13460,7 @@ function buildCanUseTool(host) {
13418
13460
  }
13419
13461
 
13420
13462
  // src/execution/query-executor.ts
13421
- var logger5 = createServiceLogger("QueryExecutor");
13463
+ var logger6 = createServiceLogger("QueryExecutor");
13422
13464
  var IMAGE_ERROR_PATTERN2 = /Could not process image/i;
13423
13465
  var RETRY_DELAYS_MS = [6e4, 12e4, 18e4, 3e5];
13424
13466
  function buildHooks(host) {
@@ -13503,7 +13545,7 @@ function resolveSessionStart(lineageKey, cwd, tui = process.env.CONVEYOR_TUI) {
13503
13545
  repairTornSessionFile(path);
13504
13546
  if (isResumableTranscript(path)) return { resume: sessionUuid };
13505
13547
  rotateSessionFile(cwd, sessionUuid);
13506
- logger5.warn("Rotated a transcript with no conversation aside before resume", { path });
13548
+ logger6.warn("Rotated a transcript with no conversation aside before resume", { path });
13507
13549
  }
13508
13550
  markSpawnedTranscript(cwd, sessionUuid);
13509
13551
  return { sessionId: sessionUuid };
@@ -13562,7 +13604,7 @@ function repairTornSessionFile(path) {
13562
13604
  }
13563
13605
  if (keepEnd === content.length) return false;
13564
13606
  truncateSync(path, Buffer.byteLength(content.slice(0, keepEnd), "utf8"));
13565
- logger5.warn("Repaired torn transcript before resume", {
13607
+ logger6.warn("Repaired torn transcript before resume", {
13566
13608
  path,
13567
13609
  trimmedBytes: content.length - keepEnd
13568
13610
  });
@@ -13649,7 +13691,7 @@ function buildQueryOptions(host, context) {
13649
13691
  disallowedTools: buildDisallowedTools(settings, mode, host.hasExitedPlanMode),
13650
13692
  enableFileCheckpointing: settings.enableFileCheckpointing,
13651
13693
  stderr: (data) => {
13652
- logger5.warn("Claude Code stderr", { data: data.trimEnd() });
13694
+ logger6.warn("Claude Code stderr", { data: data.trimEnd() });
13653
13695
  }
13654
13696
  };
13655
13697
  }
@@ -14055,7 +14097,7 @@ function rotateSessionFile(cwd, sessionUuid) {
14055
14097
  renameSync2(path, `${path.replace(/\.jsonl$/, "")}.aborted-${Date.now()}.jsonl`);
14056
14098
  return true;
14057
14099
  } catch (error) {
14058
- logger5.warn("Could not rotate an aborted transcript aside", {
14100
+ logger6.warn("Could not rotate an aborted transcript aside", {
14059
14101
  path,
14060
14102
  error: error instanceof Error ? error.message : String(error)
14061
14103
  });
@@ -14119,7 +14161,7 @@ async function handleAuthError(context, host, options) {
14119
14161
  async function handleStaleSession(context, host, options) {
14120
14162
  context.claudeSessionId = null;
14121
14163
  host.connection.storeSessionId("");
14122
- logger5.warn("Resume target had no resumable conversation; rotating it aside and starting fresh", {
14164
+ logger6.warn("Resume target had no resumable conversation; rotating it aside and starting fresh", {
14123
14165
  taskId: context.taskId,
14124
14166
  runnerMode: host.config.mode,
14125
14167
  agentMode: host.agentMode
@@ -14309,7 +14351,7 @@ async function runWithRetry(initialQuery, context, host, options) {
14309
14351
  }
14310
14352
 
14311
14353
  // src/runner/query-bridge.ts
14312
- var logger6 = createServiceLogger("QueryBridge");
14354
+ var logger7 = createServiceLogger("QueryBridge");
14313
14355
  function resolveHarnessKind() {
14314
14356
  if (process.env.CONVEYOR_FORCE_SDK_CARDS === "1") return "sdk";
14315
14357
  return "pty";
@@ -14439,7 +14481,7 @@ var QueryBridge = class {
14439
14481
  this.freshPrewarmedInitial = warmed;
14440
14482
  return warmed;
14441
14483
  } catch (err) {
14442
- logger6.warn("TUI prewarm failed; continuing with normal startup", {
14484
+ logger7.warn("TUI prewarm failed; continuing with normal startup", {
14443
14485
  error: err instanceof Error ? err.message : String(err)
14444
14486
  });
14445
14487
  return false;
@@ -14524,9 +14566,9 @@ var QueryBridge = class {
14524
14566
  const msg = err instanceof Error ? err.message : String(err);
14525
14567
  const isAbort = this._stopped || /abort/i.test(msg);
14526
14568
  if (isAbort) {
14527
- logger6.info("Query stopped by user", { error: msg });
14569
+ logger7.info("Query stopped by user", { error: msg });
14528
14570
  } else {
14529
- logger6.error("Query execution failed", { error: msg });
14571
+ logger7.error("Query execution failed", { error: msg });
14530
14572
  this.connection.sendEvent({ type: "error", message: msg });
14531
14573
  }
14532
14574
  } finally {
@@ -14552,9 +14594,9 @@ var QueryBridge = class {
14552
14594
  const msg = err instanceof Error ? err.message : String(err);
14553
14595
  const isAbort = this._stopped || /abort/i.test(msg);
14554
14596
  if (isAbort) {
14555
- logger6.info("Passive turn stopped", { error: msg });
14597
+ logger7.info("Passive turn stopped", { error: msg });
14556
14598
  } else {
14557
- logger6.error("Passive turn failed", { error: msg });
14599
+ logger7.error("Passive turn failed", { error: msg });
14558
14600
  this.connection.sendEvent({ type: "error", message: msg });
14559
14601
  }
14560
14602
  } finally {
@@ -14678,127 +14720,12 @@ var QueryBridge = class {
14678
14720
  // src/execution/usage-sampler.ts
14679
14721
  import { existsSync as existsSync2 } from "fs";
14680
14722
 
14681
- // src/usage/reset-parse.ts
14682
- var MONTHS = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
14683
- function tzOffsetMs(utcMs, tz) {
14684
- const dtf = new Intl.DateTimeFormat("en-US", {
14685
- timeZone: tz,
14686
- hour12: false,
14687
- year: "numeric",
14688
- month: "2-digit",
14689
- day: "2-digit",
14690
- hour: "2-digit",
14691
- minute: "2-digit",
14692
- second: "2-digit"
14693
- });
14694
- const map = {};
14695
- for (const part of dtf.formatToParts(new Date(utcMs))) {
14696
- if (part.type !== "literal") map[part.type] = part.value;
14697
- }
14698
- const hour = map.hour === "24" ? 0 : Number(map.hour);
14699
- const asUtc = Date.UTC(
14700
- Number(map.year),
14701
- Number(map.month) - 1,
14702
- Number(map.day),
14703
- hour,
14704
- Number(map.minute),
14705
- Number(map.second)
14706
- );
14707
- return asUtc - utcMs;
14708
- }
14709
- function zonedWallClockToUtc(y, mo, d, h, mi, tz) {
14710
- if (tz === "UTC" || tz === "Etc/UTC") return Date.UTC(y, mo, d, h, mi);
14711
- const naiveUtc = Date.UTC(y, mo, d, h, mi);
14712
- const firstGuess = naiveUtc - tzOffsetMs(naiveUtc, tz);
14713
- return naiveUtc - tzOffsetMs(firstGuess, tz);
14714
- }
14715
- function localTimeZone() {
14716
- try {
14717
- return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
14718
- } catch {
14719
- return "UTC";
14720
- }
14721
- }
14722
- function parseResetInstant(rowText, now, defaultTz = localTimeZone()) {
14723
- const m = /resets\s+(?:by\s+)?(?:([a-z]{3})\s+(\d{1,2})(?:,|\s+at)?\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)(?:\s*\(([^)]+)\))?/i.exec(
14724
- rowText
14725
- );
14726
- if (!m) return null;
14727
- const [, monStr, dayStr, hourStr, minStr, meridiem, tzRaw] = m;
14728
- const tz = tzRaw ? tzRaw.trim() : defaultTz;
14729
- let hour = Number(hourStr) % 12;
14730
- if (/pm/i.test(meridiem)) hour += 12;
14731
- const minute = minStr ? Number(minStr) : 0;
14732
- const nowDate = new Date(now);
14733
- try {
14734
- if (monStr && dayStr) {
14735
- const month = MONTHS.indexOf(monStr.toLowerCase());
14736
- if (month < 0) return null;
14737
- const day = Number(dayStr);
14738
- let utc2 = zonedWallClockToUtc(nowDate.getUTCFullYear(), month, day, hour, minute, tz);
14739
- if (utc2 < now - 24 * 60 * 60 * 1e3) {
14740
- utc2 = zonedWallClockToUtc(nowDate.getUTCFullYear() + 1, month, day, hour, minute, tz);
14741
- }
14742
- return new Date(utc2).toISOString();
14743
- }
14744
- let utc = zonedWallClockToUtc(
14745
- nowDate.getUTCFullYear(),
14746
- nowDate.getUTCMonth(),
14747
- nowDate.getUTCDate(),
14748
- hour,
14749
- minute,
14750
- tz
14751
- );
14752
- if (utc <= now) {
14753
- utc = zonedWallClockToUtc(
14754
- nowDate.getUTCFullYear(),
14755
- nowDate.getUTCMonth(),
14756
- nowDate.getUTCDate() + 1,
14757
- hour,
14758
- minute,
14759
- tz
14760
- );
14761
- }
14762
- return new Date(utc).toISOString();
14763
- } catch {
14764
- return null;
14765
- }
14766
- }
14767
-
14768
- // src/usage/parse-usage.ts
14769
- var ESC = "\\u001b";
14770
- var ANSI_CSI = new RegExp(`${ESC}\\[[0-9;?]*[ -/]*[@-~]`, "g");
14771
- var ANSI_OSC = new RegExp(`${ESC}\\][^\\u0007${ESC}]*(?:\\u0007|${ESC}\\\\)`, "g");
14772
- var BAR_GLYPHS = /[─-▟]/g;
14773
- function normalizeUsageText(stdout) {
14774
- return stdout.replace(ANSI_CSI, "").replace(ANSI_OSC, "").replace(BAR_GLYPHS, " ").replace(/\r/g, "");
14775
- }
14776
- function parseUsageGauges(stdout, now = Date.now(), defaultTz) {
14777
- const text = normalizeUsageText(stdout);
14778
- const rows = [
14779
- ...text.matchAll(
14780
- /Current (session|week)\s*(\([^)\n]*\))?[^%\n]*?(\d+(?:\.\d+)?)\s*%(?:\s*used)?([^\n]*?)(?=Current\s+(?:session|week)\b|\n|$)/gi
14781
- )
14782
- ];
14783
- const gauges = rows.map((m) => ({
14784
- label: `Current ${m[1].toLowerCase()}${m[2] ? ` ${m[2]}` : ""}`,
14785
- utilization: Number(m[3]) / 100,
14786
- resetsAt: parseResetInstant(m[4] ?? "", now, defaultTz)
14787
- }));
14788
- const session = gauges.find((g) => g.label.startsWith("Current session"));
14789
- const weekly = gauges.filter((g) => g.label.startsWith("Current week"));
14790
- const weeklyResets = weekly.map((g) => g.resetsAt).filter((r) => r !== null);
14791
- return {
14792
- sessionUsage: session ? session.utilization : null,
14793
- weeklyUsage: weekly.length ? Math.max(...weekly.map((g) => g.utilization)) : null,
14794
- sessionResetsAt: session?.resetsAt ?? null,
14795
- // Soonest weekly reset (all weekly rows share one instant in practice).
14796
- weeklyResetsAt: weeklyResets.length ? new Date(Math.min(...weeklyResets.map((r) => new Date(r).getTime()))).toISOString() : null,
14797
- gauges
14798
- };
14799
- }
14723
+ // src/usage/isolated-probe.ts
14724
+ import { mkdtemp as mkdtemp3, writeFile as writeFile10, copyFile, rm as rm5 } from "fs/promises";
14725
+ import { join as join21 } from "path";
14800
14726
 
14801
14727
  // src/usage/run-probe.ts
14728
+ var logger8 = createServiceLogger("usage-probe");
14802
14729
  var PROBE_TIMEOUT_MS = 3e4;
14803
14730
  var FIRST_SEND_MS = 5e3;
14804
14731
  var RESEND_INTERVAL_MS = 4e3;
@@ -14815,7 +14742,8 @@ function buildProbeEnv(env = process.env) {
14815
14742
  return clean;
14816
14743
  }
14817
14744
  function panelRendering(buf) {
14818
- return /Current session/i.test(buf) && /%/.test(buf);
14745
+ const text = flattenTerminalText(buf);
14746
+ return /Current\s*session/i.test(text) && /%/.test(text);
14819
14747
  }
14820
14748
  var UsageProbeRun = class {
14821
14749
  constructor(resolve, timing) {
@@ -14839,7 +14767,7 @@ var UsageProbeRun = class {
14839
14767
  this.childExited = true;
14840
14768
  this.finishBestEffort();
14841
14769
  });
14842
- this.hardTimer = setTimeout(() => this.finishBestEffort(), timeoutMs);
14770
+ this.hardTimer = setTimeout(() => this.finishOnTimeout(), timeoutMs);
14843
14771
  this.hardTimer.unref?.();
14844
14772
  const first = setTimeout(() => this.trySend(), this.timing.firstSendMs);
14845
14773
  first.unref?.();
@@ -14859,6 +14787,15 @@ var UsageProbeRun = class {
14859
14787
  if (this.settleTimer || !panelRendering(this.buf)) return;
14860
14788
  this.settleTimer = setTimeout(() => this.finish(this.buf), this.timing.settleMs);
14861
14789
  }
14790
+ finishOnTimeout() {
14791
+ if (!this.settled && !panelRendering(this.buf)) {
14792
+ logger8.info("usage probe timed out before the panel rendered", {
14793
+ bufferLength: this.buf.length,
14794
+ sends: this.sends
14795
+ });
14796
+ }
14797
+ this.finishBestEffort();
14798
+ }
14862
14799
  finishBestEffort() {
14863
14800
  this.finish(panelRendering(this.buf) ? this.buf : cleanTerminalOutput(this.buf, 500));
14864
14801
  }
@@ -14914,62 +14851,210 @@ async function runUsageProbe(deps = {}) {
14914
14851
  });
14915
14852
  }
14916
14853
 
14854
+ // src/usage/isolated-probe.ts
14855
+ var logger9 = createServiceLogger("isolated-probe");
14856
+ async function probeUsageIsolated(token, opts = {}) {
14857
+ let dir = null;
14858
+ try {
14859
+ dir = await mkdtemp3(join21(sessionTempBase(), "conveyor-usage-"));
14860
+ const credentials = buildSynthesizedCredentials(token, opts.now ?? Date.now());
14861
+ await writeFile10(join21(dir, ".credentials.json"), credentials, {
14862
+ encoding: "utf8",
14863
+ mode: 384
14864
+ });
14865
+ await copyFile(claudeJsonPath(), join21(dir, ".claude.json")).catch(() => {
14866
+ });
14867
+ return await runUsageProbe({ cwd: opts.cwd, env: { ...process.env, CLAUDE_CONFIG_DIR: dir } });
14868
+ } catch (error) {
14869
+ logger9.info("isolated usage probe failed", {
14870
+ error: error instanceof Error ? error.message : String(error)
14871
+ });
14872
+ return "";
14873
+ } finally {
14874
+ if (dir) await rm5(dir, { recursive: true, force: true }).catch(() => {
14875
+ });
14876
+ }
14877
+ }
14878
+
14879
+ // src/usage/reset-parse.ts
14880
+ var MONTHS = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
14881
+ function tzOffsetMs(utcMs, tz) {
14882
+ const dtf = new Intl.DateTimeFormat("en-US", {
14883
+ timeZone: tz,
14884
+ hour12: false,
14885
+ year: "numeric",
14886
+ month: "2-digit",
14887
+ day: "2-digit",
14888
+ hour: "2-digit",
14889
+ minute: "2-digit",
14890
+ second: "2-digit"
14891
+ });
14892
+ const map = {};
14893
+ for (const part of dtf.formatToParts(new Date(utcMs))) {
14894
+ if (part.type !== "literal") map[part.type] = part.value;
14895
+ }
14896
+ const hour = map.hour === "24" ? 0 : Number(map.hour);
14897
+ const asUtc = Date.UTC(
14898
+ Number(map.year),
14899
+ Number(map.month) - 1,
14900
+ Number(map.day),
14901
+ hour,
14902
+ Number(map.minute),
14903
+ Number(map.second)
14904
+ );
14905
+ return asUtc - utcMs;
14906
+ }
14907
+ function zonedWallClockToUtc(y, mo, d, h, mi, tz) {
14908
+ if (tz === "UTC" || tz === "Etc/UTC") return Date.UTC(y, mo, d, h, mi);
14909
+ const naiveUtc = Date.UTC(y, mo, d, h, mi);
14910
+ const firstGuess = naiveUtc - tzOffsetMs(naiveUtc, tz);
14911
+ return naiveUtc - tzOffsetMs(firstGuess, tz);
14912
+ }
14913
+ function localTimeZone() {
14914
+ try {
14915
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
14916
+ } catch {
14917
+ return "UTC";
14918
+ }
14919
+ }
14920
+ function parseResetInstant(rowText, now, defaultTz = localTimeZone()) {
14921
+ const m = /resets\s*(?:by\s*)?(?:([a-z]{3})\s*(\d{1,2})(?:,|\s*at)?\s*)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)(?:\s*\(([^)]+)\))?/i.exec(
14922
+ rowText
14923
+ );
14924
+ if (!m) return null;
14925
+ const [, monStr, dayStr, hourStr, minStr, meridiem, tzRaw] = m;
14926
+ const tz = tzRaw ? tzRaw.trim() : defaultTz;
14927
+ let hour = Number(hourStr) % 12;
14928
+ if (/pm/i.test(meridiem)) hour += 12;
14929
+ const minute = minStr ? Number(minStr) : 0;
14930
+ const nowDate = new Date(now);
14931
+ try {
14932
+ if (monStr && dayStr) {
14933
+ const month = MONTHS.indexOf(monStr.toLowerCase());
14934
+ if (month < 0) return null;
14935
+ const day = Number(dayStr);
14936
+ let utc2 = zonedWallClockToUtc(nowDate.getUTCFullYear(), month, day, hour, minute, tz);
14937
+ if (utc2 < now - 24 * 60 * 60 * 1e3) {
14938
+ utc2 = zonedWallClockToUtc(nowDate.getUTCFullYear() + 1, month, day, hour, minute, tz);
14939
+ }
14940
+ return new Date(utc2).toISOString();
14941
+ }
14942
+ let utc = zonedWallClockToUtc(
14943
+ nowDate.getUTCFullYear(),
14944
+ nowDate.getUTCMonth(),
14945
+ nowDate.getUTCDate(),
14946
+ hour,
14947
+ minute,
14948
+ tz
14949
+ );
14950
+ if (utc <= now) {
14951
+ utc = zonedWallClockToUtc(
14952
+ nowDate.getUTCFullYear(),
14953
+ nowDate.getUTCMonth(),
14954
+ nowDate.getUTCDate() + 1,
14955
+ hour,
14956
+ minute,
14957
+ tz
14958
+ );
14959
+ }
14960
+ return new Date(utc).toISOString();
14961
+ } catch {
14962
+ return null;
14963
+ }
14964
+ }
14965
+
14966
+ // src/usage/parse-usage.ts
14967
+ var BAR_GLYPHS = /[─-▟]/g;
14968
+ function normalizeUsageText(stdout) {
14969
+ return flattenTerminalText(stdout).replace(BAR_GLYPHS, " ").replace(/[\r\n]+/g, " ");
14970
+ }
14971
+ function parseUsageGauges(stdout, now = Date.now(), defaultTz) {
14972
+ const text = normalizeUsageText(stdout);
14973
+ const rows = [
14974
+ ...text.matchAll(
14975
+ /Current\s*(session|week)\s*(\([^)\n]*\))?[^%\n]*?(\d+(?:\.\d+)?)\s*%(?:\s*used)?([^\n]*?)(?=Current\s*(?:session|week)|\n|$)/gi
14976
+ )
14977
+ ];
14978
+ const gauges = rows.map((m) => ({
14979
+ label: `Current ${m[1].toLowerCase()}${m[2] ? ` ${m[2]}` : ""}`,
14980
+ utilization: Number(m[3]) / 100,
14981
+ resetsAt: parseResetInstant(m[4] ?? "", now, defaultTz)
14982
+ }));
14983
+ const session = gauges.find((g) => g.label.startsWith("Current session"));
14984
+ const weekly = gauges.filter((g) => g.label.startsWith("Current week"));
14985
+ const weeklyResets = weekly.map((g) => g.resetsAt).filter((r) => r !== null);
14986
+ return {
14987
+ sessionUsage: session ? session.utilization : null,
14988
+ weeklyUsage: weekly.length ? Math.max(...weekly.map((g) => g.utilization)) : null,
14989
+ sessionResetsAt: session?.resetsAt ?? null,
14990
+ // Soonest weekly reset (all weekly rows share one instant in practice).
14991
+ weeklyResetsAt: weeklyResets.length ? new Date(Math.min(...weeklyResets.map((r) => new Date(r).getTime()))).toISOString() : null,
14992
+ gauges
14993
+ };
14994
+ }
14995
+
14917
14996
  // src/execution/usage-sampler.ts
14918
- var logger7 = createServiceLogger("usage-sampler");
14997
+ var logger10 = createServiceLogger("usage-sampler");
14919
14998
  var NO_SAMPLES = { samples: [], unmeasurable: null };
14920
14999
  function usesNativeUsageReporting(env = process.env) {
14921
15000
  return env.CONVEYOR_TUI === "codex";
14922
15001
  }
14923
- function isAttributable(identity, sessionToken) {
15002
+ function isAttributable(identity) {
14924
15003
  if (!identity) return { ok: true };
14925
15004
  if (identity.hasRefreshToken && !identity.isConveyorOwned) {
14926
15005
  return { ok: false, reason: "manual-login-credentials" };
14927
15006
  }
14928
- if (!(identity.hasRefreshToken && identity.isConveyorOwned) && sessionToken && identity.accessToken && identity.accessToken !== sessionToken) {
14929
- return { ok: false, reason: "credentials-token-mismatch" };
14930
- }
14931
15007
  return { ok: true };
14932
15008
  }
14933
- async function sampleKeyUsage(token, probe = () => runUsageProbe(), hasSubscriptionCredentials = () => existsSync2(claudeCredentialsPath()), readIdentity = readCredentialsIdentity) {
14934
- if (!token && !hasSubscriptionCredentials()) return NO_SAMPLES;
15009
+ function samplesFromPanel(stdout) {
15010
+ const { sessionUsage, weeklyUsage, sessionResetsAt, weeklyResetsAt, gauges } = parseUsageGauges(stdout);
15011
+ const samples = [];
15012
+ if (sessionUsage !== null) {
15013
+ samples.push({
15014
+ rateLimitType: "five_hour",
15015
+ utilization: sessionUsage,
15016
+ status: "allowed",
15017
+ resetsAt: sessionResetsAt,
15018
+ gauges
15019
+ });
15020
+ }
15021
+ if (weeklyUsage !== null) {
15022
+ samples.push({
15023
+ rateLimitType: "seven_day",
15024
+ utilization: weeklyUsage,
15025
+ status: "allowed",
15026
+ resetsAt: weeklyResetsAt,
15027
+ gauges
15028
+ });
15029
+ }
15030
+ if (samples.length === 0) {
15031
+ logger10.info("usage sample produced no gauges", {
15032
+ stdoutLength: stdout.length,
15033
+ stdoutHead: stdout.slice(0, 200).replaceAll("\n", " ")
15034
+ });
15035
+ }
15036
+ return samples;
15037
+ }
15038
+ async function sampleKeyUsage(opts = {}) {
15039
+ const { token, cwd } = opts;
15040
+ const hasCredentialsFile = opts.hasCredentialsFile ?? (() => existsSync2(claudeCredentialsPath()));
15041
+ if (!token && !hasCredentialsFile()) return NO_SAMPLES;
14935
15042
  try {
14936
- const attributable = isAttributable(await readIdentity(), token);
15043
+ if (token) {
15044
+ const probeToken = opts.probeToken ?? ((own) => probeUsageIsolated(own, { cwd }));
15045
+ return { samples: samplesFromPanel(await probeToken(token)), unmeasurable: null };
15046
+ }
15047
+ const attributable = isAttributable(await (opts.readIdentity ?? readCredentialsIdentity)());
14937
15048
  if (!attributable.ok) {
14938
- logger7.info("usage sample skipped \u2014 credentials not attributable to this session's key", {
15049
+ logger10.info("usage sample skipped \u2014 credentials not attributable to this session's key", {
14939
15050
  reason: attributable.reason
14940
15051
  });
14941
15052
  return { samples: [], unmeasurable: { reason: attributable.reason ?? "unattributable" } };
14942
15053
  }
14943
- const stdout = await probe();
14944
- const { sessionUsage, weeklyUsage, sessionResetsAt, weeklyResetsAt, gauges } = parseUsageGauges(stdout);
14945
- const samples = [];
14946
- if (sessionUsage !== null) {
14947
- samples.push({
14948
- rateLimitType: "five_hour",
14949
- utilization: sessionUsage,
14950
- status: "allowed",
14951
- resetsAt: sessionResetsAt,
14952
- gauges
14953
- });
14954
- }
14955
- if (weeklyUsage !== null) {
14956
- samples.push({
14957
- rateLimitType: "seven_day",
14958
- utilization: weeklyUsage,
14959
- status: "allowed",
14960
- resetsAt: weeklyResetsAt,
14961
- gauges
14962
- });
14963
- }
14964
- if (samples.length === 0) {
14965
- logger7.info("usage sample produced no gauges", {
14966
- stdoutLength: stdout.length,
14967
- stdoutHead: stdout.slice(0, 200).replaceAll("\n", " ")
14968
- });
14969
- }
14970
- return { samples, unmeasurable: null };
15054
+ const probeFile = opts.probeCredentialsFile ?? (() => runUsageProbe({ cwd }));
15055
+ return { samples: samplesFromPanel(await probeFile()), unmeasurable: null };
14971
15056
  } catch (error) {
14972
- logger7.info("usage sample failed", {
15057
+ logger10.info("usage sample failed", {
14973
15058
  error: error instanceof Error ? error.message : String(error)
14974
15059
  });
14975
15060
  return NO_SAMPLES;
@@ -16338,10 +16423,10 @@ var SessionRunner = class _SessionRunner {
16338
16423
  async sampleAndReportKeyUsage() {
16339
16424
  if (this.stopped || usesNativeUsageReporting()) return;
16340
16425
  const codingAgentKeyId = process.env.CONVEYOR_CODING_AGENT_KEY_ID;
16341
- const { samples, unmeasurable } = await sampleKeyUsage(
16342
- process.env.CLAUDE_CODE_OAUTH_TOKEN,
16343
- () => runUsageProbe({ cwd: this.config.workspaceDir })
16344
- );
16426
+ const { samples, unmeasurable } = await sampleKeyUsage({
16427
+ token: process.env.CLAUDE_CODE_OAUTH_TOKEN,
16428
+ cwd: this.config.workspaceDir
16429
+ });
16345
16430
  if (unmeasurable) {
16346
16431
  this.connection.sendEvent(buildUnmeasurableEvent(unmeasurable.reason, codingAgentKeyId));
16347
16432
  return;
@@ -16868,8 +16953,6 @@ function unshallowRepo(workspaceDir) {
16868
16953
 
16869
16954
  export {
16870
16955
  isPermissionDeniedError,
16871
- buildSynthesizedCredentials,
16872
- claudeJsonPath,
16873
16956
  PtyHarness,
16874
16957
  resolveTuiKindFromEnv,
16875
16958
  resolveTuiAdapter,
@@ -16878,8 +16961,8 @@ export {
16878
16961
  buildProjectTools,
16879
16962
  resolvePlaywrightMcpServer,
16880
16963
  resolveSessionStart,
16964
+ probeUsageIsolated,
16881
16965
  parseUsageGauges,
16882
- runUsageProbe,
16883
16966
  usesNativeUsageReporting,
16884
16967
  sampleKeyUsage,
16885
16968
  buildRateLimitEvents,