@rallycry/conveyor-agent 11.0.18 → 11.0.20

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.
@@ -2,7 +2,7 @@ import {
2
2
  mapChatHistory,
3
3
  readAgentVersion,
4
4
  refreshSkillsAfterCheckout
5
- } from "./chunk-SR66HQKB.js";
5
+ } from "./chunk-37J5MMQT.js";
6
6
  import {
7
7
  MAX_BETWEEN_TURN_BUFFER,
8
8
  MAX_DIAGNOSTIC_OUTPUT,
@@ -59,11 +59,11 @@ import {
59
59
  statWorkspacePath,
60
60
  updateRemoteToken,
61
61
  verifyGitCredential
62
- } from "./chunk-WS7QRB37.js";
62
+ } from "./chunk-JBGPARLG.js";
63
63
  import {
64
64
  registerBootMilestoneSocketFallback,
65
65
  reportBootMilestone
66
- } from "./chunk-GL2DIQEQ.js";
66
+ } from "./chunk-Q4FQOJ7D.js";
67
67
  import {
68
68
  describeTokenFile,
69
69
  ghHostsExternallyOwned,
@@ -1416,6 +1416,7 @@ var ListProjectSessionGroupsRequestSchema = z5.object({
1416
1416
  projectId: z5.string()
1417
1417
  });
1418
1418
  var ListMyLiveSessionsAcrossProjectsRequestSchema = z5.object({});
1419
+ var ListSessionGroupsAcrossProjectsRequestSchema = z5.object({});
1419
1420
  var GetProjectAvailableTuisRequestSchema = z5.object({
1420
1421
  projectId: z5.string()
1421
1422
  });
@@ -2042,6 +2043,11 @@ var ANTHROPIC_CATALOG = [
2042
2043
  anthropicEntry(DEFAULT_HAIKU_MODEL, "Haiku 4.5", 1, 5, { supportsEffort: false }),
2043
2044
  anthropicEntry(FABLE_MODEL, "Fable 5.1", 10, 50)
2044
2045
  ];
2046
+ var CODEX_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
2047
+ var DEFAULT_CODEX_CODING_MODEL = "gpt-5.6-terra";
2048
+ function isCodexReasoningEffort(value) {
2049
+ return typeof value === "string" && CODEX_REASONING_EFFORTS.includes(value);
2050
+ }
2045
2051
  var HUMAN_PROSE_WRITING_STYLE = `## Writing style for humans
2046
2052
  When you write prose a person will read \u2014 chat messages, plan updates, PR titles and bodies, PR review guides (the \`publish_review_guide\` overview and section explanations), review comments \u2014 follow these rules (based on ASD-STE100 Simplified Technical English):
2047
2053
  - Use active voice. Say who does what ("The API rejects the request", not "the request is rejected").
@@ -3251,6 +3257,13 @@ var JsonlTailer = class {
3251
3257
  };
3252
3258
 
3253
3259
  // src/harness/codex/event-source.ts
3260
+ var USAGE_CAP_PATTERN = /usage_limit_(?:reached|exceeded)|rate_limit_reached|UsageLimitReached|hit your usage limit/i;
3261
+ var WEEKLY_WINDOW_MINUTES = 24 * 60;
3262
+ function isUsageCap(error) {
3263
+ return [error.codex_error_info, error.code, error.type, error.message].some(
3264
+ (value) => typeof value === "string" && USAGE_CAP_PATTERN.test(value)
3265
+ );
3266
+ }
3254
3267
  var CodexEventSource = class {
3255
3268
  constructor(emit, onSession, chat) {
3256
3269
  this.emit = emit;
@@ -3263,6 +3276,8 @@ var CodexEventSource = class {
3263
3276
  sessionId = null;
3264
3277
  completed = /* @__PURE__ */ new Set();
3265
3278
  transcript = null;
3279
+ /** Last reported reading per rate-limit window, keyed by `five_hour` / `seven_day`. */
3280
+ windows = /* @__PURE__ */ new Map();
3266
3281
  handleRecord(raw) {
3267
3282
  if (!raw || typeof raw !== "object") return;
3268
3283
  const e = raw;
@@ -3317,11 +3332,16 @@ var CodexEventSource = class {
3317
3332
  if (record.type !== "event_msg" || !record.payload || typeof record.payload !== "object")
3318
3333
  return;
3319
3334
  const event = record.payload;
3335
+ if (event.type === "token_count") {
3336
+ this.handleTokenCount(event);
3337
+ return;
3338
+ }
3320
3339
  if (event.type !== "task_complete" || !event.error || typeof event.error !== "object") return;
3321
3340
  const turn = event.turn_id;
3322
3341
  if (typeof turn !== "string" || this.completed.has(turn)) return;
3323
3342
  this.rememberTurn(turn);
3324
3343
  const error = event.error;
3344
+ if (isUsageCap(error)) this.emitUsageCap();
3325
3345
  this.emit({
3326
3346
  type: "result",
3327
3347
  subtype: "error",
@@ -3329,6 +3349,59 @@ var CodexEventSource = class {
3329
3349
  });
3330
3350
  this.chat({ kind: "turn_end" });
3331
3351
  }
3352
+ /**
3353
+ * `token_count` carries the account's rate-limit gauges on every turn:
3354
+ * `rate_limits.primary` (the 5-hour window) and `.secondary` (weekly), each
3355
+ * with `used_percent`, `window_minutes` and `resets_at` in epoch seconds.
3356
+ * Classified by window length rather than by name, and reported only when a
3357
+ * window's numbers changed — the event fires per turn, and every report
3358
+ * rewrites the key row and fans out `project:codingAgentKeyUpdated`.
3359
+ */
3360
+ handleTokenCount(event) {
3361
+ const limits = event.rate_limits;
3362
+ if (!limits || typeof limits !== "object") return;
3363
+ for (const name of ["primary", "secondary"]) {
3364
+ const window = limits[name];
3365
+ if (!window || typeof window !== "object") continue;
3366
+ const reading = window;
3367
+ if (typeof reading.used_percent !== "number") continue;
3368
+ const minutes = typeof reading.window_minutes === "number" ? reading.window_minutes : name === "secondary" ? WEEKLY_WINDOW_MINUTES : 0;
3369
+ const rateLimitType = minutes >= WEEKLY_WINDOW_MINUTES ? "seven_day" : "five_hour";
3370
+ const resetsAt = typeof reading.resets_at === "number" ? reading.resets_at : void 0;
3371
+ const last = this.windows.get(rateLimitType);
3372
+ if (last && last.usedPercent === reading.used_percent && last.resetsAt === resetsAt) continue;
3373
+ this.windows.set(rateLimitType, { usedPercent: reading.used_percent, resetsAt });
3374
+ this.emit({
3375
+ type: "rate_limit_event",
3376
+ rate_limit_info: {
3377
+ status: "allowed",
3378
+ rateLimitType,
3379
+ utilization: Math.max(0, Math.min(1, reading.used_percent / 100)),
3380
+ resetsAt
3381
+ }
3382
+ });
3383
+ }
3384
+ }
3385
+ /**
3386
+ * The CLI does not say which window capped. The fullest window last reported
3387
+ * is the one that did; with no reading at all, the 5-hour window is the
3388
+ * conservative guess (its fallback pause is the shorter one).
3389
+ */
3390
+ emitUsageCap() {
3391
+ let rateLimitType = "five_hour";
3392
+ let resetsAt;
3393
+ let fullest = -Infinity;
3394
+ for (const [type, window] of this.windows) {
3395
+ if (window.usedPercent <= fullest) continue;
3396
+ fullest = window.usedPercent;
3397
+ rateLimitType = type;
3398
+ resetsAt = window.resetsAt;
3399
+ }
3400
+ this.emit({
3401
+ type: "rate_limit_event",
3402
+ rate_limit_info: { status: "rejected", rateLimitType, resetsAt }
3403
+ });
3404
+ }
3332
3405
  rememberTurn(turn) {
3333
3406
  this.completed.add(turn);
3334
3407
  const oldest = this.completed.values().next().value;
@@ -4774,7 +4847,8 @@ var ClaudeTuiAdapter = class {
4774
4847
  structuredEvents: true,
4775
4848
  prefill: true,
4776
4849
  passiveTurns: true,
4777
- rawPromptGate: false
4850
+ rawPromptGate: false,
4851
+ prewarm: true
4778
4852
  };
4779
4853
  resolveBinary(env = process.env) {
4780
4854
  return env.CONVEYOR_CLAUDE_BIN ?? "claude";
@@ -4869,7 +4943,6 @@ var PtySession = class {
4869
4943
  // Raw-TUI input detection (see awaitRawTuiInputLive). `probeWindow`, when
4870
4944
  // non-null, accumulates raw output so a probe can look for its own sentinel
4871
4945
  // coming back.
4872
- spawnedAt = 0;
4873
4946
  // Set once the child writes a DEC private mode — it owns the tty from then on,
4874
4947
  // so the kernel no longer echoes our keystrokes and a sentinel coming back is
4875
4948
  // attributable to the app's own repaint.
@@ -4929,6 +5002,8 @@ var PtySession = class {
4929
5002
  turn;
4930
5003
  codexPromptInArgv = false;
4931
5004
  codexInitialPrompt;
5005
+ /** A fresh CLI intentionally started without input for checkout overlap. */
5006
+ prewarmed = false;
4932
5007
  codexSource = null;
4933
5008
  adapterCleanupPaths = [];
4934
5009
  onIdle(listener) {
@@ -5022,7 +5097,7 @@ var PtySession = class {
5022
5097
  * never set) keeps the Claude semantics byte-identical.
5023
5098
  */
5024
5099
  canReuse(resume, fingerprint) {
5025
- const resumeMatches = resume === void 0 ? this.reportedOpenCodeId !== null : resume === this.sessionUuid;
5100
+ const resumeMatches = resume === void 0 ? this.reportedOpenCodeId !== null || this.prewarmed : resume === this.sessionUuid;
5026
5101
  return !this._toreDown && !this.exited && this.activeQueue === null && // A parked questionnaire owns the terminal — a fed prompt would type
5027
5102
  // into the dialog. Force teardown + respawn instead.
5028
5103
  !this.questionnairePending && resumeMatches && fingerprint === this.spawnFingerprint;
@@ -5052,6 +5127,7 @@ var PtySession = class {
5052
5127
  if (this.betweenTurnBuffer.length > MAX_BETWEEN_TURN_BUFFER) {
5053
5128
  this.betweenTurnBuffer.shift();
5054
5129
  }
5130
+ if (this.prewarmed) return;
5055
5131
  if (!this.passiveSignaled) {
5056
5132
  this.passiveSignaled = true;
5057
5133
  this.passiveListener?.();
@@ -5063,8 +5139,10 @@ var PtySession = class {
5063
5139
  * the prompt into the live pty. The caller must have verified canReuse().
5064
5140
  */
5065
5141
  async beginTurn(prompt, options) {
5142
+ this.prewarmed = false;
5066
5143
  this.turnPrompt = prompt;
5067
5144
  this.turn = turnOptionsFrom(options);
5145
+ this.attachInput();
5068
5146
  this.resetForTurn();
5069
5147
  this.betweenTurnBuffer = [];
5070
5148
  this.activeQueue = new AsyncEventQueue();
@@ -5155,13 +5233,24 @@ var PtySession = class {
5155
5233
  }
5156
5234
  }
5157
5235
  async start() {
5236
+ await this.startInternal(true);
5237
+ }
5238
+ /** Start the CLI and its event plumbing without allocating a model turn. */
5239
+ async startParked() {
5240
+ this.prewarmed = true;
5241
+ await this.startInternal(false);
5242
+ }
5243
+ // oxlint-disable-next-line complexity -- adapter startup and turn gating share one teardown boundary
5244
+ async startInternal(feedPrompt) {
5158
5245
  await this.collectCodexPrompt();
5159
5246
  const sessionId = this.resume ?? this.options.sessionId;
5160
5247
  if (!sessionId) {
5161
5248
  throw new Error("PtySession requires options.sessionId or a resume target");
5162
5249
  }
5163
- this.activeQueue = new AsyncEventQueue();
5164
- this.turnStream = this.activeQueue.drain();
5250
+ if (feedPrompt) {
5251
+ this.activeQueue = new AsyncEventQueue();
5252
+ this.turnStream = this.activeQueue.drain();
5253
+ }
5165
5254
  const signal = this.turn.abortController?.signal;
5166
5255
  if (signal?.aborted) {
5167
5256
  await this.teardown();
@@ -5187,7 +5276,7 @@ var PtySession = class {
5187
5276
  model: this.options.model
5188
5277
  });
5189
5278
  }
5190
- if (signal) {
5279
+ if (signal && feedPrompt) {
5191
5280
  this.abortHandler = () => {
5192
5281
  void this.teardown();
5193
5282
  };
@@ -5198,10 +5287,10 @@ var PtySession = class {
5198
5287
  }
5199
5288
  }
5200
5289
  if (this.bridge) {
5201
- this.unsubInput = this.bridge.onInput((data) => this.writeStdin(data));
5290
+ if (feedPrompt) this.attachInput();
5202
5291
  this.unsubResize = this.bridge.onResize((cols, rows) => this.resizePty(cols, rows));
5203
5292
  }
5204
- await this.feedPrompt();
5293
+ if (feedPrompt) await this.feedPrompt();
5205
5294
  } catch (err) {
5206
5295
  await this.teardown();
5207
5296
  throw err;
@@ -5241,7 +5330,11 @@ var PtySession = class {
5241
5330
  session_id: id,
5242
5331
  model: this.options.model
5243
5332
  });
5244
- this.sendChatEvent({ kind: "init", model: this.options.model, claudeSessionId: id });
5333
+ this.sendChatEvent({
5334
+ kind: "init",
5335
+ model: this.options.model,
5336
+ claudeSessionId: id
5337
+ });
5245
5338
  },
5246
5339
  (event) => this.sendChatEvent(event)
5247
5340
  );
@@ -5279,7 +5372,11 @@ var PtySession = class {
5279
5372
  session_id: id,
5280
5373
  model: this.options.model
5281
5374
  });
5282
- this.sendChatEvent({ kind: "init", model: this.options.model, claudeSessionId: id });
5375
+ this.sendChatEvent({
5376
+ kind: "init",
5377
+ model: this.options.model,
5378
+ claudeSessionId: id
5379
+ });
5283
5380
  },
5284
5381
  (event) => this.sendChatEvent(event)
5285
5382
  );
@@ -5393,9 +5490,19 @@ var PtySession = class {
5393
5490
  this.pendingSyntheticQuestionIds = [];
5394
5491
  this.questionResultRemap.clear();
5395
5492
  for (const toolUseId of orphaned) {
5396
- this.sendChatEvent({ kind: "tool_result", toolUseId, output: "", isError: false });
5493
+ this.sendChatEvent({
5494
+ kind: "tool_result",
5495
+ toolUseId,
5496
+ output: "",
5497
+ isError: false
5498
+ });
5397
5499
  }
5398
5500
  }
5501
+ /** Attach relay keystrokes only once a Git-gated turn is allowed to accept them. */
5502
+ attachInput() {
5503
+ if (!this.bridge || this.unsubInput) return;
5504
+ this.unsubInput = this.bridge.onInput((data) => this.writeStdin(data));
5505
+ }
5399
5506
  writeStdin(text) {
5400
5507
  this.pty?.write(text);
5401
5508
  }
@@ -5562,7 +5669,6 @@ var PtySession = class {
5562
5669
  pty.onExit((event) => {
5563
5670
  void this.finalizeOnExit(event.exitCode);
5564
5671
  });
5565
- this.spawnedAt = Date.now();
5566
5672
  this.sawTerminalSetup = false;
5567
5673
  this.probeWindow = null;
5568
5674
  this.wroteToProcess = false;
@@ -5606,7 +5712,7 @@ var PtySession = class {
5606
5712
  if (!needsRawReadyGate(this.adapter.capabilities)) return;
5607
5713
  const timing = resolveRawTuiProbeTiming();
5608
5714
  const { sentinel } = timing;
5609
- const start = this.spawnedAt || Date.now();
5715
+ const start = Date.now();
5610
5716
  const setupDeadline = start + timing.firstOutputMaxMs;
5611
5717
  while (!this._toreDown && Date.now() < setupDeadline) {
5612
5718
  if (this.sawTerminalSetup) break;
@@ -5890,7 +5996,12 @@ var PtySession = class {
5890
5996
  }
5891
5997
  if (!this.adapter.capabilities.structuredEvents) {
5892
5998
  if (exitCode === 0) {
5893
- this.pushEvent({ type: "result", subtype: "success", result: "", total_cost_usd: 0 });
5999
+ this.pushEvent({
6000
+ type: "result",
6001
+ subtype: "success",
6002
+ result: "",
6003
+ total_cost_usd: 0
6004
+ });
5894
6005
  } else {
5895
6006
  this.pushEvent({
5896
6007
  type: "result",
@@ -6051,6 +6162,9 @@ var PtyHarness = class _PtyHarness {
6051
6162
  authNoticeSent = false;
6052
6163
  /** Once-per-process guard on the pod-recycle escalation (see escalateToPodRecycle). */
6053
6164
  recycleRequested = false;
6165
+ disposed = false;
6166
+ prewarming = null;
6167
+ prewarmInFlight = null;
6054
6168
  /**
6055
6169
  * Wiggle the live pty's size so the CLI repaints its whole screen. No-op on
6056
6170
  * the SDK harness. Falls back to the parked session so an API reconnect while
@@ -6111,6 +6225,60 @@ var PtyHarness = class _PtyHarness {
6111
6225
  yield* this.drain(session);
6112
6226
  yield* this.recoverFailedDelivery(session, options, want);
6113
6227
  }
6228
+ /**
6229
+ * Prepare a fresh TUI while another boot task runs. This deliberately does
6230
+ * not send stdin or create an active event queue, so no model/tool work can
6231
+ * begin before the runner releases its checkout and WIP gates.
6232
+ */
6233
+ async prewarm(options) {
6234
+ if (this.prewarmInFlight) return this.prewarmInFlight;
6235
+ const pending = this.startPrewarm(options);
6236
+ this.prewarmInFlight = pending;
6237
+ try {
6238
+ return await pending;
6239
+ } finally {
6240
+ this.prewarmInFlight = null;
6241
+ }
6242
+ }
6243
+ async startPrewarm(options) {
6244
+ if (this.disposed || !this.adapter.capabilities.prewarm) return false;
6245
+ const startedAt = Date.now();
6246
+ const prepared = ensureSessionTarget(options, void 0);
6247
+ if (prepared !== options) {
6248
+ _PtyHarness.log.warn("prewarm had no session target \u2014 minted one", {
6249
+ sessionId: prepared.sessionId
6250
+ });
6251
+ }
6252
+ const fingerprint = this.fingerprintOf(prepared);
6253
+ if (this.parked?.canReuse(void 0, fingerprint) && !await this.parkedHomeDied(prepared)) {
6254
+ _PtyHarness.log.info("TUI prewarm reused parked process", {
6255
+ elapsedMs: Date.now() - startedAt
6256
+ });
6257
+ return true;
6258
+ }
6259
+ if (this.parked) {
6260
+ const stale = this.parked;
6261
+ this.parked = null;
6262
+ this.cancelEndedTimer();
6263
+ await stale.teardown();
6264
+ }
6265
+ if (this.disposed) return false;
6266
+ _PtyHarness.log.info("TUI prewarm starting fresh process");
6267
+ const session = await this.spawnSession("", prepared, void 0, true, (created) => {
6268
+ this.prewarming = created;
6269
+ });
6270
+ this.prewarming = null;
6271
+ if (this.disposed) {
6272
+ await session.teardown();
6273
+ this.notifyEnded();
6274
+ return false;
6275
+ }
6276
+ this.park(session);
6277
+ _PtyHarness.log.info("TUI prewarm process started", {
6278
+ elapsedMs: Date.now() - startedAt
6279
+ });
6280
+ return true;
6281
+ }
6114
6282
  /**
6115
6283
  * Did the shared `~/.claude` mount die while this session sat parked?
6116
6284
  *
@@ -6147,8 +6315,9 @@ var PtyHarness = class _PtyHarness {
6147
6315
  * environment preparation (config-home health, credential synthesis,
6148
6316
  * auth-readiness warning) and registering the parked-death watch.
6149
6317
  */
6150
- async spawnSession(prompt, options, want) {
6318
+ async spawnSession(prompt, options, want, parked = false, onAllocated) {
6151
6319
  const session = new PtySession(prompt, options, want, this.bridge, this.adapter);
6320
+ onAllocated?.(session);
6152
6321
  if (this.ownsClaudeConfigHome) {
6153
6322
  await ensureUsableClaudeConfigHome(options.cwd, _PtyHarness.log);
6154
6323
  }
@@ -6157,7 +6326,8 @@ var PtyHarness = class _PtyHarness {
6157
6326
  await this.warnIfAuthNotReady();
6158
6327
  }
6159
6328
  session.onExit(() => this.handleSessionExit(session));
6160
- await session.start();
6329
+ if (parked) await session.startParked();
6330
+ else await session.start();
6161
6331
  return session;
6162
6332
  }
6163
6333
  /**
@@ -6329,10 +6499,21 @@ var PtyHarness = class _PtyHarness {
6329
6499
  * the Connected-TUI tab hides promptly.
6330
6500
  */
6331
6501
  async dispose() {
6502
+ this.disposed = true;
6332
6503
  this.cancelEndedTimer();
6333
- const toKill = [this.parked, this.activeSession].filter((s) => s !== null);
6504
+ try {
6505
+ await this.prewarmInFlight;
6506
+ } catch (error) {
6507
+ _PtyHarness.log.warn("TUI prewarm failed during disposal", {
6508
+ error: error instanceof Error ? error.message : String(error)
6509
+ });
6510
+ }
6511
+ const toKill = [.../* @__PURE__ */ new Set([this.parked, this.activeSession, this.prewarming])].filter(
6512
+ (s) => s !== null
6513
+ );
6334
6514
  this.parked = null;
6335
6515
  this.activeSession = null;
6516
+ this.prewarming = null;
6336
6517
  for (const session of toKill) {
6337
6518
  try {
6338
6519
  await session.teardown();
@@ -6471,13 +6652,16 @@ async function seedOpenCodeOauth(env) {
6471
6652
  }
6472
6653
 
6473
6654
  // src/harness/openai-model.ts
6474
- var DEFAULT_OPENAI_CODING_MODEL = "gpt-5.6-terra";
6655
+ var DEFAULT_OPENAI_CODING_MODEL = DEFAULT_CODEX_CODING_MODEL;
6475
6656
  function resolveOpenAiCodingModel(model) {
6476
6657
  if (!model || model.startsWith("claude-") || model.includes("/") && !model.startsWith("openai/")) {
6477
6658
  return DEFAULT_OPENAI_CODING_MODEL;
6478
6659
  }
6479
6660
  return model.replace(/^openai\//, "");
6480
6661
  }
6662
+ function resolveCodexReasoningEffort(...candidates) {
6663
+ return candidates.find(isCodexReasoningEffort);
6664
+ }
6481
6665
 
6482
6666
  // src/harness/opencode/credentials.ts
6483
6667
  var PROVIDER_KEY_ENV = {
@@ -6771,7 +6955,8 @@ var OpenCodeTuiAdapter = class {
6771
6955
  // opencode silently discards early stdin while the TUI paints; the pasted
6772
6956
  // text itself is lost, so the readiness probe must gate the first write
6773
6957
  // even though structured events exist now.
6774
- rawPromptGate: true
6958
+ rawPromptGate: true,
6959
+ prewarm: true
6775
6960
  };
6776
6961
  resolveBinary(env = this.env) {
6777
6962
  const override = env.CONVEYOR_OPENCODE_BIN;
@@ -6935,7 +7120,8 @@ var CodexTuiAdapter = class {
6935
7120
  structuredEvents: true,
6936
7121
  prefill: true,
6937
7122
  passiveTurns: true,
6938
- rawPromptGate: true
7123
+ rawPromptGate: true,
7124
+ prewarm: true
6939
7125
  };
6940
7126
  resolveBinary(env = this.env) {
6941
7127
  const path = findOnPath(env.CONVEYOR_CODEX_BIN ?? "codex", env);
@@ -6987,11 +7173,16 @@ var CodexTuiAdapter = class {
6987
7173
  sandbox_mode: "danger-full-access",
6988
7174
  developer_instructions: [
6989
7175
  input.options.appendSystemPrompt,
6990
- "Conveyor workflow skills are stored in .claude/skills in the workspace. When instructed to run a /skill-name skill, read .claude/skills/skill-name/SKILL.md and follow its instructions. This is a file-based workflow, not a native slash command."
7176
+ "Conveyor workflow skills are stored in .claude/skills in the workspace. When instructed to run a /skill-name skill, read .claude/skills/skill-name/SKILL.md and follow its instructions. This is a file-based workflow, not a native slash command. When a skill's Goal and finish line section tells you to create a thread goal, do so with your goal tool before the first step; the objective is that section's finish line."
6991
7177
  ].filter(Boolean).join("\n\n"),
6992
7178
  mcp_servers: codexMcpConfig(input.mcpEntries ?? {}),
6993
7179
  projects: { [trustedCwd]: { trust_level: "trusted" } }
6994
7180
  };
7181
+ const effort = resolveCodexReasoningEffort(
7182
+ this.env.CONVEYOR_AGENT_REASONING_EFFORT,
7183
+ input.options.codex?.effort
7184
+ );
7185
+ if (effort) config.model_reasoning_effort = effort;
6995
7186
  if (input.pluginPath && input.eventsSinkPath) {
6996
7187
  const profile = basename(dirname3(input.pluginPath));
6997
7188
  const profilePath = join14(env.CODEX_HOME, `${profile}.config.toml`);
@@ -7009,7 +7200,9 @@ var CodexTuiAdapter = class {
7009
7200
  if (input.pluginPath) args.push("--profile", basename(dirname3(input.pluginPath)));
7010
7201
  for (const [key, value] of Object.entries(config))
7011
7202
  args.push("-c", `${key}=${tomlValue(value)}`);
7012
- const model = resolveOpenAiCodingModel(this.env.CONVEYOR_AGENT_MODEL ?? input.options.model);
7203
+ const model = resolveOpenAiCodingModel(
7204
+ this.env.CONVEYOR_AGENT_MODEL ?? input.options.codex?.model ?? input.options.model
7205
+ );
7013
7206
  args.push("--model", model);
7014
7207
  if (input.resume) args.push("resume", input.resume);
7015
7208
  if (input.initialPrompt) args.push("--", input.initialPrompt);
@@ -8103,6 +8296,7 @@ function buildParentReviewPrompt() {
8103
8296
  var TYPE_PRIORITY = { rule: 0, doc: 1, file: 2, folder: 3 };
8104
8297
  var SUMMARY_SCAN_CHARS = 4e3;
8105
8298
  var SUMMARY_MAX_CHARS = 160;
8299
+ var CONTEXT_READ_CONCURRENCY = 4;
8106
8300
  var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
8107
8301
  ".png",
8108
8302
  ".jpg",
@@ -8205,24 +8399,61 @@ async function readFolderListing(folderPath) {
8205
8399
  return null;
8206
8400
  }
8207
8401
  }
8208
- async function resolveEntry(entry) {
8209
- const result = {
8210
- type: entry.type,
8211
- path: entry.path,
8212
- label: entry.label,
8213
- summary: null
8214
- };
8215
- if (entry.label) {
8216
- result.summary = truncateSummary(entry.label);
8402
+ var ContextReadCoordinator = class {
8403
+ availableSlots = CONTEXT_READ_CONCURRENCY;
8404
+ waitingReaders = [];
8405
+ pendingSummaries = /* @__PURE__ */ new Map();
8406
+ async resolveEntry(entry) {
8407
+ const result = {
8408
+ type: entry.type,
8409
+ path: entry.path,
8410
+ label: entry.label,
8411
+ summary: null
8412
+ };
8413
+ if (entry.label) {
8414
+ result.summary = truncateSummary(entry.label);
8415
+ return result;
8416
+ }
8417
+ result.summary = await this.readSummary(entry);
8217
8418
  return result;
8218
8419
  }
8219
- if (entry.type === "folder") {
8220
- result.summary = await readFolderListing(entry.path);
8221
- return result;
8420
+ readSummary(entry) {
8421
+ const kind = entry.type === "folder" ? "folder" : "file";
8422
+ const key = `${kind}:${entry.path}`;
8423
+ const pending = this.pendingSummaries.get(key);
8424
+ if (pending) return pending;
8425
+ const read = this.limit(
8426
+ () => kind === "folder" ? readFolderListing(entry.path) : readFileSummary(entry.path)
8427
+ );
8428
+ this.pendingSummaries.set(key, read);
8429
+ return read;
8222
8430
  }
8223
- result.summary = await readFileSummary(entry.path);
8224
- return result;
8225
- }
8431
+ async limit(read) {
8432
+ await this.acquireSlot();
8433
+ try {
8434
+ return await read();
8435
+ } finally {
8436
+ this.releaseSlot();
8437
+ }
8438
+ }
8439
+ async acquireSlot() {
8440
+ if (this.availableSlots > 0) {
8441
+ this.availableSlots -= 1;
8442
+ return;
8443
+ }
8444
+ await new Promise((resolve) => {
8445
+ this.waitingReaders.push(resolve);
8446
+ });
8447
+ }
8448
+ releaseSlot() {
8449
+ const nextReader = this.waitingReaders.shift();
8450
+ if (nextReader) {
8451
+ nextReader();
8452
+ return;
8453
+ }
8454
+ this.availableSlots += 1;
8455
+ }
8456
+ };
8226
8457
  function formatEntry(entry) {
8227
8458
  const suffix = entry.summary ? ` \u2014 ${entry.summary}` : "";
8228
8459
  return `- \`${entry.path}\`${suffix}`;
@@ -8277,39 +8508,33 @@ function formatResolvedTags(resolved, subProject, mentioned, runnerMode) {
8277
8508
  }
8278
8509
  return parts.join("\n");
8279
8510
  }
8280
- async function resolveEntries(contextPaths) {
8281
- if (!contextPaths?.length) return [];
8511
+ function resolveEntries(contextPaths, coordinator) {
8512
+ if (!contextPaths?.length) return Promise.resolve([]);
8282
8513
  const sorted = [...contextPaths].sort(
8283
8514
  (a, b) => (TYPE_PRIORITY[a.type] ?? 99) - (TYPE_PRIORITY[b.type] ?? 99)
8284
8515
  );
8285
- const results = [];
8286
- for (const entry of sorted) {
8287
- results.push(await resolveEntry(entry));
8288
- }
8289
- return results;
8516
+ return Promise.all(sorted.map((entry) => coordinator.resolveEntry(entry)));
8290
8517
  }
8291
8518
  function countResolved(entries) {
8292
8519
  const injected = entries.filter((e) => e.summary !== null).length;
8293
8520
  return { injected, skipped: entries.length - injected };
8294
8521
  }
8295
- async function resolveAssignedTags(assignedTags) {
8296
- const resolved = [];
8297
- let injected = 0;
8298
- let skipped = 0;
8299
- for (const tag of assignedTags) {
8300
- const entries = await resolveEntries(tag.contextPaths);
8301
- const counts = countResolved(entries);
8302
- injected += counts.injected;
8303
- skipped += counts.skipped;
8304
- resolved.push({
8522
+ async function resolveAssignedTags(assignedTags, coordinator) {
8523
+ const resolved = await Promise.all(
8524
+ assignedTags.map(async (tag) => ({
8305
8525
  tagName: tag.name,
8306
8526
  description: tag.description,
8307
- entries,
8527
+ entries: await resolveEntries(tag.contextPaths, coordinator),
8308
8528
  hasOverview: tag.hasOverview,
8309
8529
  overviewPath: tag.overviewPath ?? null
8310
- });
8311
- }
8312
- return { resolved, injected, skipped };
8530
+ }))
8531
+ );
8532
+ const counts = resolved.map((tag) => countResolved(tag.entries));
8533
+ return {
8534
+ resolved,
8535
+ injected: counts.reduce((total, current) => total + current.injected, 0),
8536
+ skipped: counts.reduce((total, current) => total + current.skipped, 0)
8537
+ };
8313
8538
  }
8314
8539
  async function resolveTagContext(projectTags, taskTagIds, _model, _betas, runnerMode, subProject, mentionedTagIds) {
8315
8540
  const taskTagIdSet = new Set(taskTagIds);
@@ -8323,30 +8548,26 @@ async function resolveTagContext(projectTags, taskTagIds, _model, _betas, runner
8323
8548
  if (!hasTagPaths && !hasSubProjectPaths && mentionedTags.length === 0) {
8324
8549
  return { injectedSection: "", stats: { injected: 0, skipped: 0 } };
8325
8550
  }
8326
- const {
8327
- resolved,
8328
- injected: tagInjected,
8329
- skipped: tagSkipped
8330
- } = await resolveAssignedTags(assignedTags);
8331
- const { resolved: mentionedResolved } = await resolveAssignedTags(mentionedTags);
8332
- let subProjectResolved = null;
8333
- let subInjected = 0;
8334
- let subSkipped = 0;
8335
- if (subProject && hasSubProjectPaths) {
8336
- const entries = await resolveEntries(subProject.contextPaths);
8337
- const counts = countResolved(entries);
8338
- subInjected = counts.injected;
8339
- subSkipped = counts.skipped;
8340
- subProjectResolved = { name: subProject.name, entries };
8341
- }
8551
+ const coordinator = new ContextReadCoordinator();
8552
+ const subProjectEntries = subProject && hasSubProjectPaths ? resolveEntries(subProject.contextPaths, coordinator) : Promise.resolve(null);
8553
+ const [assigned, mentioned, subProjectResolvedEntries] = await Promise.all([
8554
+ resolveAssignedTags(assignedTags, coordinator),
8555
+ resolveAssignedTags(mentionedTags, coordinator),
8556
+ subProjectEntries
8557
+ ]);
8558
+ const subProjectResolved = subProject && subProjectResolvedEntries ? { name: subProject.name, entries: subProjectResolvedEntries } : null;
8559
+ const subProjectCounts = subProjectResolvedEntries ? countResolved(subProjectResolvedEntries) : { injected: 0, skipped: 0 };
8342
8560
  return {
8343
8561
  injectedSection: formatResolvedTags(
8344
- resolved,
8562
+ assigned.resolved,
8345
8563
  subProjectResolved,
8346
- mentionedResolved,
8564
+ mentioned.resolved,
8347
8565
  runnerMode
8348
8566
  ),
8349
- stats: { injected: tagInjected + subInjected, skipped: tagSkipped + subSkipped }
8567
+ stats: {
8568
+ injected: assigned.injected + subProjectCounts.injected,
8569
+ skipped: assigned.skipped + subProjectCounts.skipped
8570
+ }
8350
8571
  };
8351
8572
  }
8352
8573
 
@@ -13243,17 +13464,21 @@ function handleRateLimitEvent(event, host) {
13243
13464
  const { rate_limit_info } = event;
13244
13465
  logger4.info("Rate limit event received", { rate_limit_info });
13245
13466
  const status = rate_limit_info.status;
13467
+ const resetsAt = epochSecondsToISO(rate_limit_info.resetsAt);
13246
13468
  const utilization = rate_limit_info.utilization ?? (status === "rejected" ? 1 : void 0);
13247
13469
  if (utilization !== void 0 && rate_limit_info.rateLimitType) {
13248
13470
  host.connection.sendEvent({
13249
13471
  type: "rate_limit_update",
13250
13472
  rateLimitType: rate_limit_info.rateLimitType,
13251
13473
  utilization,
13252
- status
13474
+ status,
13475
+ // The server parses this into the key's sessionResetsAt / weeklyResetsAt;
13476
+ // Codex reports a reset instant with every gauge, so allowed events carry
13477
+ // it too, not only rejections.
13478
+ ...resetsAt ? { resetsAt } : {}
13253
13479
  });
13254
13480
  }
13255
13481
  if (status === "rejected") {
13256
- const resetsAt = epochSecondsToISO(rate_limit_info.resetsAt);
13257
13482
  const resetsAtDisplay = resetsAt ?? "unknown";
13258
13483
  const message = `Rate limit rejected (type: ${rate_limit_info.rateLimitType ?? "unknown"}, resets at: ${resetsAtDisplay})`;
13259
13484
  host.connection.sendEvent({ type: "error", message });
@@ -14120,7 +14345,11 @@ function buildQueryOptions(host, context) {
14120
14345
  const systemPromptText = buildSystemPrompt(
14121
14346
  host.config.mode,
14122
14347
  context,
14123
- { ...host.config, isAuto: host.isAuto, runtimeTui: process.env.CONVEYOR_TUI },
14348
+ {
14349
+ ...host.config,
14350
+ isAuto: host.isAuto,
14351
+ runtimeTui: process.env.CONVEYOR_TUI
14352
+ },
14124
14353
  host.setupLog,
14125
14354
  mode
14126
14355
  );
@@ -14163,6 +14392,7 @@ function buildQueryOptions(host, context) {
14163
14392
  effort: settings.effort,
14164
14393
  thinking: settings.thinking,
14165
14394
  betas: settings.betas,
14395
+ codex: settings.codex,
14166
14396
  abortController: host.abortController ?? void 0,
14167
14397
  disallowedTools: buildDisallowedTools(settings, mode, host.hasExitedPlanMode),
14168
14398
  enableFileCheckpointing: settings.enableFileCheckpointing,
@@ -14180,7 +14410,11 @@ function buildMultimodalPrompt(textPrompt, context, skipImages = false) {
14180
14410
  for (const msg of context.chatHistory) {
14181
14411
  for (const f2 of msg.files ?? []) {
14182
14412
  if (f2.content && f2.contentEncoding === "base64") {
14183
- chatImages.push({ fileName: f2.fileName, mimeType: f2.mimeType, content: f2.content });
14413
+ chatImages.push({
14414
+ fileName: f2.fileName,
14415
+ mimeType: f2.mimeType,
14416
+ content: f2.content
14417
+ });
14184
14418
  }
14185
14419
  }
14186
14420
  }
@@ -14195,14 +14429,24 @@ function buildMultimodalPrompt(textPrompt, context, skipImages = false) {
14195
14429
  data: file.content ?? ""
14196
14430
  }
14197
14431
  });
14198
- blocks.push({ type: "text", text: `[Attached image: ${file.fileName} (${file.mimeType})]` });
14432
+ blocks.push({
14433
+ type: "text",
14434
+ text: `[Attached image: ${file.fileName} (${file.mimeType})]`
14435
+ });
14199
14436
  }
14200
14437
  for (const file of chatImages) {
14201
14438
  blocks.push({
14202
14439
  type: "image",
14203
- source: { type: "base64", media_type: file.mimeType, data: file.content }
14440
+ source: {
14441
+ type: "base64",
14442
+ media_type: file.mimeType,
14443
+ data: file.content
14444
+ }
14445
+ });
14446
+ blocks.push({
14447
+ type: "text",
14448
+ text: `[Chat image: ${file.fileName} (${file.mimeType})]`
14204
14449
  });
14205
- blocks.push({ type: "text", text: `[Chat image: ${file.fileName} (${file.mimeType})]` });
14206
14450
  }
14207
14451
  return blocks;
14208
14452
  }
@@ -14318,6 +14562,7 @@ async function* watchForParkedTui(inner, host, opts) {
14318
14562
  const timer = setTimeout(() => {
14319
14563
  parked = true;
14320
14564
  host.connection.emitStatus("waiting_for_input");
14565
+ host.callbacks.onSubmittedPtyParked?.();
14321
14566
  void host.callbacks.onStatusChange("waiting_for_input");
14322
14567
  }, PARKED_TUI_GRACE_MS);
14323
14568
  const silenceTimeoutMs = resolveTurnSilenceTimeoutMs();
@@ -14359,7 +14604,7 @@ async function* watchForParkedTui(inner, host, opts) {
14359
14604
  function takesPlannerOpeningTurn(host, mode) {
14360
14605
  return host.config.mode === "plan" && mode === "discovery";
14361
14606
  }
14362
- async function runSdkQuery(host, context, followUpContent, promptDeliveryOverride) {
14607
+ async function runSdkQuery(host, context, followUpContent, promptDeliveryOverride, freshPrewarmed = false) {
14363
14608
  if (host.isStopped()) return;
14364
14609
  const mode = host.agentMode;
14365
14610
  const isDiscoveryLike = mode === "discovery" || mode === "help";
@@ -14368,7 +14613,7 @@ async function runSdkQuery(host, context, followUpContent, promptDeliveryOverrid
14368
14613
  host.config.workspaceDir,
14369
14614
  host.config.mode === "code-review" && process.env.CONVEYOR_TUI !== "codex" ? "claude-code" : process.env.CONVEYOR_TUI
14370
14615
  );
14371
- const hasExistingSession = !!sessionStart.resume;
14616
+ const hasExistingSession = !!sessionStart.resume && !freshPrewarmed;
14372
14617
  const promptDelivery = promptDeliveryOverride ?? resolvePromptDelivery({
14373
14618
  harnessKind: host.harnessKind,
14374
14619
  runnerMode: host.config.mode,
@@ -14390,11 +14635,49 @@ async function runSdkQuery(host, context, followUpContent, promptDeliveryOverrid
14390
14635
  if (isDiscoveryLike && host.harnessKind === "sdk") {
14391
14636
  return;
14392
14637
  }
14393
- if (isDiscoveryLike && resume && !takesPlannerOpeningTurn(host, mode)) {
14638
+ if (isDiscoveryLike && resume && !freshPrewarmed && !takesPlannerOpeningTurn(host, mode)) {
14394
14639
  return;
14395
14640
  }
14396
14641
  await runInitialQuery(host, context, options, resume, promptDelivery);
14397
14642
  }
14643
+ async function prewarmInitialTuiQuery(host, context) {
14644
+ if (host.harnessKind !== "pty" || !host.harness.prewarm) return false;
14645
+ const mode = host.agentMode;
14646
+ const sessionStart = resolveSessionStart(
14647
+ sessionLineageKey(context.taskId, mode, host.config.mode),
14648
+ host.config.workspaceDir,
14649
+ host.config.mode === "code-review" && process.env.CONVEYOR_TUI !== "codex" ? "claude-code" : process.env.CONVEYOR_TUI
14650
+ );
14651
+ if (sessionStart.resume) return false;
14652
+ const promptDelivery = resolvePromptDelivery({
14653
+ harnessKind: host.harnessKind,
14654
+ runnerMode: host.config.mode,
14655
+ isAuto: host.isAuto,
14656
+ agentMode: mode,
14657
+ isFollowUp: false,
14658
+ hasExistingSession: false
14659
+ });
14660
+ const options = {
14661
+ ...buildQueryOptions(host, context),
14662
+ promptDelivery,
14663
+ ...sessionStart.sessionId ? { sessionId: sessionStart.sessionId } : {}
14664
+ };
14665
+ const initialPrompt = await buildInitialPrompt(
14666
+ host.config.mode,
14667
+ context,
14668
+ host.isAuto,
14669
+ host.agentMode,
14670
+ host.config.packExecution
14671
+ );
14672
+ const { appendSystemPrompt } = selectInitialPromptInput(
14673
+ promptDelivery,
14674
+ initialPrompt,
14675
+ context,
14676
+ options.appendSystemPrompt,
14677
+ host.harnessKind
14678
+ );
14679
+ return host.harness.prewarm({ ...options, appendSystemPrompt });
14680
+ }
14398
14681
  async function runFollowUpQuery(host, context, options, resume, followUpContent) {
14399
14682
  if (options.promptDelivery === "prefill") {
14400
14683
  await runPrefilledFollowUp(host, context, options, resume, followUpContent);
@@ -14720,7 +15003,10 @@ function handleRetryError(error, context, host, options, prevImageError) {
14720
15003
  return handleAuthError(context, host, options);
14721
15004
  }
14722
15005
  if (!isRetriableError(error)) throw error;
14723
- return { action: "continue", lastErrorWasImage: classifyImageError(error) || prevImageError };
15006
+ return {
15007
+ action: "continue",
15008
+ lastErrorWasImage: classifyImageError(error) || prevImageError
15009
+ };
14724
15010
  }
14725
15011
  function handleProcessResult(result, context, host, options) {
14726
15012
  if (result.modeRestart || host.isStopped()) return { action: "return" };
@@ -14737,10 +15023,16 @@ function handleProcessResult(result, context, host, options) {
14737
15023
  };
14738
15024
  }
14739
15025
  if (result.staleSession && context.claudeSessionId) {
14740
- return { action: "return_promise", promise: handleStaleSession(context, host, options) };
15026
+ return {
15027
+ action: "return_promise",
15028
+ promise: handleStaleSession(context, host, options)
15029
+ };
14741
15030
  }
14742
15031
  if (result.authError) {
14743
- return { action: "return_promise", promise: handleAuthError(context, host, options) };
15032
+ return {
15033
+ action: "return_promise",
15034
+ promise: handleAuthError(context, host, options)
15035
+ };
14744
15036
  }
14745
15037
  if (!result.retriable) return { action: "return" };
14746
15038
  return {
@@ -14848,6 +15140,7 @@ var QueryBridge = class {
14848
15140
  _apiOutageDetail = null;
14849
15141
  _keyCycleCount = 0;
14850
15142
  _abortController = null;
15143
+ freshPrewarmedInitial = false;
14851
15144
  /** Called by SessionRunner when ExitPlanMode triggers a mode transition. */
14852
15145
  onModeTransition;
14853
15146
  /** Called by tool handlers to soft-stop (abort query, keep session alive). */
@@ -14903,6 +15196,19 @@ var QueryBridge = class {
14903
15196
  forceRepaint() {
14904
15197
  this.harness.forceRepaint?.();
14905
15198
  }
15199
+ /** Warm a fresh interactive task TUI only. It receives no input until SessionRunner's Git gate releases. */
15200
+ async prewarmInitial(context) {
15201
+ try {
15202
+ const warmed = await prewarmInitialTuiQuery(this.buildHost(), context);
15203
+ this.freshPrewarmedInitial = warmed;
15204
+ return warmed;
15205
+ } catch (err) {
15206
+ logger6.warn("TUI prewarm failed; continuing with normal startup", {
15207
+ error: err instanceof Error ? err.message : String(err)
15208
+ });
15209
+ return false;
15210
+ }
15211
+ }
14906
15212
  stop() {
14907
15213
  this._stopped = true;
14908
15214
  this._abortController?.abort();
@@ -14952,7 +15258,7 @@ var QueryBridge = class {
14952
15258
  isAuto: this.mode.isAuto,
14953
15259
  agentMode: this.mode.effectiveMode,
14954
15260
  isFollowUp: false,
14955
- hasExistingSession: hasExistingSessionFile(context.taskId, this.runnerConfig.workspaceDir, {
15261
+ hasExistingSession: !this.freshPrewarmedInitial && hasExistingSessionFile(context.taskId, this.runnerConfig.workspaceDir, {
14956
15262
  agentMode: this.mode.effectiveMode,
14957
15263
  runnerMode: this.runnerConfig.mode
14958
15264
  })
@@ -14975,7 +15281,9 @@ var QueryBridge = class {
14975
15281
  this._abortController = new AbortController();
14976
15282
  const host = this.buildHost();
14977
15283
  try {
14978
- await runSdkQuery(host, context, followUpContent, promptDelivery);
15284
+ const freshPrewarmed = !followUpContent && this.freshPrewarmedInitial;
15285
+ this.freshPrewarmedInitial = false;
15286
+ await runSdkQuery(host, context, followUpContent, promptDelivery, freshPrewarmed);
14979
15287
  } catch (err) {
14980
15288
  const msg = err instanceof Error ? err.message : String(err);
14981
15289
  const isAbort = this._stopped || /abort/i.test(msg);
@@ -15373,6 +15681,9 @@ async function runUsageProbe(deps = {}) {
15373
15681
  // src/execution/usage-sampler.ts
15374
15682
  var logger7 = createServiceLogger("usage-sampler");
15375
15683
  var NO_SAMPLES = { samples: [], unmeasurable: null };
15684
+ function usesNativeUsageReporting(env = process.env) {
15685
+ return env.CONVEYOR_TUI === "codex";
15686
+ }
15376
15687
  function isAttributable(identity, sessionToken) {
15377
15688
  if (!identity) return { ok: true };
15378
15689
  if (identity.hasRefreshToken && !identity.isConveyorOwned) {
@@ -15689,7 +16000,10 @@ var SessionRunner = class _SessionRunner {
15689
16000
  });
15690
16001
  const initialMode = config.agentMode ?? (config.runnerMode === "pm" ? config.isAuto ? "auto" : "discovery" : "building");
15691
16002
  this.mode = new ModeController(initialMode, config.runnerMode, config.isAuto);
15692
- const lifecycleConfig = { ...DEFAULT_LIFECYCLE_CONFIG, ...config.lifecycle };
16003
+ const lifecycleConfig = {
16004
+ ...DEFAULT_LIFECYCLE_CONFIG,
16005
+ ...config.lifecycle
16006
+ };
15693
16007
  this.lifecycle = new Lifecycle(lifecycleConfig, {
15694
16008
  onHeartbeat: () => {
15695
16009
  const loopStatus = this.refreshLoopStatus();
@@ -16048,6 +16362,10 @@ var SessionRunner = class _SessionRunner {
16048
16362
  await this.shutdown("error");
16049
16363
  return;
16050
16364
  }
16365
+ this.mode.applyServerMode(this.fullContext?.agentMode, this.fullContext?.isAuto);
16366
+ this.mode.resolveInitialMode(this.taskContext);
16367
+ this.queryBridge = this.createQueryBridge();
16368
+ const prewarmInitial = this.queryBridge.prewarmInitial(this.fullContext);
16051
16369
  const gitState = await awaitGitReady({
16052
16370
  onLog: (m) => process.stderr.write(`[conveyor-agent] ${m}
16053
16371
  `)
@@ -16056,6 +16374,7 @@ var SessionRunner = class _SessionRunner {
16056
16374
  const message = gitState === "failed" ? "Workspace git preparation failed (see pod logs)" : "Workspace git preparation timed out (see pod logs)";
16057
16375
  this.connection.sendEvent({ type: "error", message });
16058
16376
  await this.callbacks.onEvent({ type: "error", message });
16377
+ await this.queryBridge.dispose();
16059
16378
  await this.shutdown("error");
16060
16379
  return;
16061
16380
  }
@@ -16069,8 +16388,8 @@ var SessionRunner = class _SessionRunner {
16069
16388
  process.stderr.write("[conveyor-agent] WARNING: task-branch checkout failed\n");
16070
16389
  }
16071
16390
  if (ok) void reportBootMilestone({ key: "branch_ready" });
16072
- await this.refreshSkillsForCheckout();
16073
16391
  }
16392
+ if (this.fullContext?.githubBranch) await this.refreshSkillsForCheckout();
16074
16393
  if (!this.stopped) {
16075
16394
  this.lifecycle.startGitFlush();
16076
16395
  }
@@ -16085,13 +16404,15 @@ var SessionRunner = class _SessionRunner {
16085
16404
  }
16086
16405
  }
16087
16406
  this.workspaceCommands?.notifyWorkspaceReady();
16088
- this.mode.applyServerMode(this.fullContext?.agentMode, this.fullContext?.isAuto);
16089
- this.mode.resolveInitialMode(this.taskContext);
16090
16407
  if (this.fullContext?.isAuto && PRE_BUILD_TASK_STATUSES.has(this.taskContext.status) && this.mode.isBuildCapable && hasTaskPlan(this.fullContext.plan)) {
16091
16408
  void this.connection.triggerIdentification().catch(() => {
16092
16409
  });
16093
16410
  }
16094
- this.queryBridge = this.createQueryBridge();
16411
+ await prewarmInitial;
16412
+ if (this.stopped) {
16413
+ await this.queryBridge.dispose();
16414
+ return;
16415
+ }
16095
16416
  this.logInitialization();
16096
16417
  const staleBatch = [...this.pendingMessages];
16097
16418
  const didExecuteInitialQuery = await this.executeInitialMode();
@@ -16284,7 +16605,11 @@ var SessionRunner = class _SessionRunner {
16284
16605
  }
16285
16606
  if (delivery === "prefill") {
16286
16607
  await this.setState("waiting_for_input");
16287
- await this.callbacks.onEvent({ type: "execute_mode", mode: effectiveMode, delivery });
16608
+ await this.callbacks.onEvent({
16609
+ type: "execute_mode",
16610
+ mode: effectiveMode,
16611
+ delivery
16612
+ });
16288
16613
  if (this.pendingMessages.length > 0) {
16289
16614
  if (!this.stopped) await this.setState("idle");
16290
16615
  return false;
@@ -16297,7 +16622,10 @@ var SessionRunner = class _SessionRunner {
16297
16622
  }
16298
16623
  } else {
16299
16624
  await this.setState("running");
16300
- await this.callbacks.onEvent({ type: "execute_mode", mode: effectiveMode });
16625
+ await this.callbacks.onEvent({
16626
+ type: "execute_mode",
16627
+ mode: effectiveMode
16628
+ });
16301
16629
  await this.executeQuery(void 0, delivery);
16302
16630
  await this.requeueWedgedInitialQuery(delivery);
16303
16631
  }
@@ -16606,7 +16934,7 @@ var SessionRunner = class _SessionRunner {
16606
16934
  * `selectBestKey` rotation honest. Best-effort — never throws, no-op when the
16607
16935
  * pod has no OAuth token (e.g. API-key projects). */
16608
16936
  async sampleAndReportKeyUsage() {
16609
- if (this.stopped) return;
16937
+ if (this.stopped || usesNativeUsageReporting()) return;
16610
16938
  const codingAgentKeyId = process.env.CONVEYOR_CODING_AGENT_KEY_ID;
16611
16939
  const { samples, unmeasurable } = await sampleKeyUsage(
16612
16940
  process.env.CLAUDE_CODE_OAUTH_TOKEN,
@@ -16754,16 +17082,8 @@ var SessionRunner = class _SessionRunner {
16754
17082
  isAuto: this.config.isAuto
16755
17083
  };
16756
17084
  const bridge = new QueryBridge(this.connection, this.mode, runnerConfig, {
16757
- onStatusChange: (status) => {
16758
- if (status === "running") {
16759
- this.beginRunningEpisode();
16760
- if (this._state === "waiting_for_input") {
16761
- this._state = "running";
16762
- this.lifecycle.cancelIdleTimer();
16763
- }
16764
- }
16765
- return this.callbacks.onStatusChange(status);
16766
- },
17085
+ onStatusChange: (status) => this.handleQueryStatus(status),
17086
+ onSubmittedPtyParked: () => this.applySubmittedPtyParkedStatus(),
16767
17087
  onEvent: (event) => {
16768
17088
  if (!this.agentLiveReported) {
16769
17089
  this.agentLiveReported = true;
@@ -16788,7 +17108,11 @@ var SessionRunner = class _SessionRunner {
16788
17108
  const oldMode = this.mode.effectiveMode;
16789
17109
  process.stderr.write(`[conveyor-agent] Mode transition: ${oldMode} \u2192 ${newMode}
16790
17110
  `);
16791
- this.connection.sendEvent({ type: "mode_transition", from: oldMode, to: newMode });
17111
+ this.connection.sendEvent({
17112
+ type: "mode_transition",
17113
+ from: oldMode,
17114
+ to: newMode
17115
+ });
16792
17116
  this.mode.pendingModeRestart = true;
16793
17117
  this.connection.emitModeChanged(newMode);
16794
17118
  this.softStop();
@@ -17016,6 +17340,32 @@ ${outcome.failures.join("\n")}
17016
17340
  await this.connection.emitStatus(status);
17017
17341
  await this.callbacks.onStatusChange(status);
17018
17342
  }
17343
+ /**
17344
+ * A submitted PTY query can park on an unexpected terminal dialog without
17345
+ * returning its turn. Keep the runner's local state (which drives heartbeat
17346
+ * liveness) in sync with that specific watchdog signal, but do not emit it
17347
+ * again: QueryBridge already sent it.
17348
+ *
17349
+ * Retry delays and pending questionnaires also report `waiting_for_input`,
17350
+ * but they retain their existing bounded liveness holds and must not change
17351
+ * `_state` here.
17352
+ */
17353
+ applySubmittedPtyParkedStatus() {
17354
+ this._state = "waiting_for_input";
17355
+ this.refreshLoopStatus();
17356
+ }
17357
+ /** Forward an ordinary query status without changing liveness for transient
17358
+ * waiting states. The submitted-PTY watchdog calls its dedicated handler. */
17359
+ handleQueryStatus(status) {
17360
+ if (status === "running") {
17361
+ this.beginRunningEpisode();
17362
+ if (this._state === "waiting_for_input") {
17363
+ this._state = "running";
17364
+ this.lifecycle.cancelIdleTimer();
17365
+ }
17366
+ }
17367
+ return this.callbacks.onStatusChange(status);
17368
+ }
17019
17369
  /** Mark a fresh turn without emitting a duplicate status. QueryExecutor's
17020
17370
  * prefilled PTY callback already emitted `running` before it reaches here. */
17021
17371
  beginRunningEpisode() {
@@ -17121,6 +17471,7 @@ export {
17121
17471
  resolveSessionStart,
17122
17472
  parseUsageGauges,
17123
17473
  runUsageProbe,
17474
+ usesNativeUsageReporting,
17124
17475
  sampleKeyUsage,
17125
17476
  buildRateLimitEvents,
17126
17477
  buildUnmeasurableEvent,