@rallycry/conveyor-agent 9.0.3 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -378,6 +378,14 @@ var AgentConnection = class _AgentConnection {
378
378
  static RECONNECT_MAX_DELAY_MS = 6e4;
379
379
  static RECONNECT_STATUS_EVERY_N = 3;
380
380
  isReconnecting = false;
381
+ /**
382
+ * Invoked after every successful session reconnect (the `connectAgent` RPC
383
+ * re-established the session room). The runner uses this to force a TUI
384
+ * repaint: the reconnect may have landed on a different/restarted API
385
+ * process whose PTY scrollback ring is empty, and a quiet terminal would
386
+ * otherwise never re-seed it.
387
+ */
388
+ onReconnected;
381
389
  async reconnectToSession() {
382
390
  if (this.isReconnecting) return;
383
391
  this.isReconnecting = true;
@@ -409,6 +417,10 @@ var AgentConnection = class _AgentConnection {
409
417
  reason: "reconnected",
410
418
  attempts: attempt
411
419
  });
420
+ try {
421
+ this.onReconnected?.();
422
+ } catch {
423
+ }
412
424
  return;
413
425
  } catch (err) {
414
426
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -541,24 +553,23 @@ var AgentConnection = class _AgentConnection {
541
553
  };
542
554
  }
543
555
  // ── Convenience methods (thin wrappers around call / emit) ─────────
544
- async emitStatus(status) {
556
+ async emitStatus(status, reason) {
545
557
  this.lastEmittedStatus = status;
546
558
  await this.flushEvents();
559
+ const payload = {
560
+ sessionId: this.config.sessionId,
561
+ status,
562
+ ...reason ? { reason } : {}
563
+ };
547
564
  const AWAIT_STATUSES = ["idle", "waiting_for_input", "connected"];
548
565
  if (AWAIT_STATUSES.includes(status)) {
549
566
  try {
550
- await this.call("reportAgentStatus", {
551
- sessionId: this.config.sessionId,
552
- status
553
- });
567
+ await this.call("reportAgentStatus", payload);
554
568
  this.lastReportedStatus = status;
555
569
  } catch {
556
570
  }
557
571
  } else {
558
- void this.call("reportAgentStatus", {
559
- sessionId: this.config.sessionId,
560
- status
561
- }).then(() => {
572
+ void this.call("reportAgentStatus", payload).then(() => {
562
573
  this.lastReportedStatus = status;
563
574
  }).catch(() => {
564
575
  });
@@ -668,17 +679,6 @@ var AgentConnection = class _AgentConnection {
668
679
  this.sendEvent({ type: "agent_typing_stop" });
669
680
  }
670
681
  // ── RPC convenience wrappers (v6 compat, will migrate to call()) ───
671
- trackSpending(params) {
672
- if (!this.socket) return;
673
- void this.call("trackSpending", {
674
- sessionId: this.config.sessionId,
675
- inputTokens: params.modelUsage?.reduce((s, m) => s + (m.inputTokens ?? 0), 0) ?? 0,
676
- outputTokens: params.modelUsage?.reduce((s, m) => s + (m.outputTokens ?? 0), 0) ?? 0,
677
- costUsd: params.totalCostUsd,
678
- model: params.modelUsage?.[0]?.model ?? "unknown"
679
- }).catch(() => {
680
- });
681
- }
682
682
  emitRateLimitPause(resetsAt) {
683
683
  this.sendEvent({ type: "rate_limit_update", resetsAt });
684
684
  }
@@ -723,9 +723,6 @@ ${q.question}${q.options.length ? "\n" + q.options.map((o) => `- ${o.label}: ${o
723
723
  triggerIdentification() {
724
724
  return this.call("triggerIdentification", { sessionId: this.config.sessionId });
725
725
  }
726
- getCumulativeSpending() {
727
- return this.call("getCumulativeSpending", { sessionId: this.config.sessionId });
728
- }
729
726
  async refreshAuthToken() {
730
727
  const result = await this.refreshFromBootstrap();
731
728
  return result.refreshedClaude;
@@ -1287,6 +1284,7 @@ const net = require("node:net");
1287
1284
  const crypto = require("node:crypto");
1288
1285
 
1289
1286
  const PRE_TOOL_USE_TIMEOUT_MS = 110000;
1287
+ const ASK_USER_QUESTION_TIMEOUT_MS = 5000;
1290
1288
 
1291
1289
  let raw = "";
1292
1290
  process.stdin.setEncoding("utf8");
@@ -1358,17 +1356,26 @@ function preToolUse(payload) {
1358
1356
  );
1359
1357
  process.exit(0);
1360
1358
  };
1361
- const denyUnavailable = () =>
1362
- respond(
1363
- "deny",
1364
- "Conveyor validation is unavailable right now. Wait a moment and call the tool again.",
1365
- );
1359
+ // AskUserQuestion is observe-only (status reporting) \u2014 fail OPEN so a
1360
+ // missing/slow socket never denies the questionnaire. Everything else
1361
+ // (ExitPlanMode) is a Conveyor gate \u2014 fail CLOSED.
1362
+ const failOpen = payload.tool_name === "AskUserQuestion";
1363
+ const respondUnavailable = () =>
1364
+ failOpen
1365
+ ? respond("allow")
1366
+ : respond(
1367
+ "deny",
1368
+ "Conveyor validation is unavailable right now. Wait a moment and call the tool again.",
1369
+ );
1366
1370
  if (!socketPath) {
1367
- denyUnavailable();
1371
+ respondUnavailable();
1368
1372
  return;
1369
1373
  }
1370
1374
  const id = crypto.randomBytes(8).toString("hex");
1371
- const timer = setTimeout(denyUnavailable, PRE_TOOL_USE_TIMEOUT_MS);
1375
+ const timer = setTimeout(
1376
+ respondUnavailable,
1377
+ failOpen ? ASK_USER_QUESTION_TIMEOUT_MS : PRE_TOOL_USE_TIMEOUT_MS,
1378
+ );
1372
1379
  const client = net.connect(socketPath, () => {
1373
1380
  client.write(
1374
1381
  JSON.stringify({
@@ -1403,11 +1410,11 @@ function preToolUse(payload) {
1403
1410
  });
1404
1411
  client.on("error", () => {
1405
1412
  clearTimeout(timer);
1406
- denyUnavailable();
1413
+ respondUnavailable();
1407
1414
  });
1408
1415
  client.on("close", () => {
1409
1416
  clearTimeout(timer);
1410
- denyUnavailable();
1417
+ respondUnavailable();
1411
1418
  });
1412
1419
  }
1413
1420
  `;
@@ -1445,6 +1452,13 @@ async function writeHookSettings(dir) {
1445
1452
  {
1446
1453
  matcher: "ExitPlanMode",
1447
1454
  hooks: [{ type: "command", command: `node ${JSON.stringify(helperPath)}`, timeout: 120 }]
1455
+ },
1456
+ {
1457
+ // Observe-only: lets the session report waiting_for_input the moment
1458
+ // the planning questionnaire renders. The helper fails open for this
1459
+ // tool, so the questionnaire is never blocked by Conveyor.
1460
+ matcher: "AskUserQuestion",
1461
+ hooks: [{ type: "command", command: `node ${JSON.stringify(helperPath)}`, timeout: 30 }]
1448
1462
  }
1449
1463
  ],
1450
1464
  PostToolUse: [
@@ -1682,7 +1696,9 @@ var ConnectAgentRequestSchema = z.object({
1682
1696
  });
1683
1697
  var ReportAgentStatusRequestSchema = z.object({
1684
1698
  sessionId: z.string(),
1685
- status: z.string()
1699
+ status: z.string(),
1700
+ /** Why the agent reports this status (e.g. "user_question" while an AskUserQuestion questionnaire is pending in the TUI). */
1701
+ reason: z.string().optional()
1686
1702
  });
1687
1703
  var NotifyAgentVersionRequestSchema = z.object({
1688
1704
  sessionId: z.string(),
@@ -1997,6 +2013,20 @@ var GetProjectTaskSessionsRequestSchema = z2.object({
1997
2013
  projectId: z2.string(),
1998
2014
  taskId: z2.string()
1999
2015
  });
2016
+ var QueryProjectGcpLogsRequestSchema = z2.object({
2017
+ projectId: z2.string(),
2018
+ env: z2.enum(["prod", "dev", "claudespace"]).optional(),
2019
+ severity: z2.enum(["DEBUG", "INFO", "NOTICE", "WARNING", "ERROR", "CRITICAL", "ALERT", "EMERGENCY"]).optional(),
2020
+ services: z2.array(z2.string().min(1).max(200)).max(25).optional(),
2021
+ sqlInstances: z2.array(z2.string().min(1).max(200)).max(25).optional(),
2022
+ allServices: z2.boolean().optional(),
2023
+ search: z2.string().max(256).optional(),
2024
+ filter: z2.string().max(1e3).optional(),
2025
+ startTime: z2.string().optional(),
2026
+ endTime: z2.string().optional(),
2027
+ limit: z2.number().int().min(1).max(200).optional().default(50),
2028
+ pageToken: z2.string().max(4096).optional()
2029
+ });
2000
2030
  var StartProjectBuildRequestSchema = z2.object({
2001
2031
  projectId: z2.string(),
2002
2032
  taskId: z2.string(),
@@ -2175,6 +2205,7 @@ var CreateProjectSuggestionRequestSchema = z2.object({
2175
2205
  tagNames: z2.array(z2.string()).optional(),
2176
2206
  requestingUserId: z2.string().optional()
2177
2207
  });
2208
+ var AGENT_STATUS_REASON_USER_QUESTION = "user_question";
2178
2209
  var TASK_CHAT_HISTORY_LIMIT = 20;
2179
2210
  var PM_CHAT_HISTORY_LIMIT = 40;
2180
2211
  var AGENT_CHAT_HISTORY_FETCH_LIMIT = Math.max(TASK_CHAT_HISTORY_LIMIT, PM_CHAT_HISTORY_LIMIT) + 10;
@@ -3107,6 +3138,25 @@ async function transcriptSize(path) {
3107
3138
  return 0;
3108
3139
  }
3109
3140
  }
3141
+ function parseUserQuestions(input) {
3142
+ if (!Array.isArray(input.questions)) return [];
3143
+ const questions = [];
3144
+ for (const entry of input.questions) {
3145
+ if (!isRecord2(entry)) continue;
3146
+ if (typeof entry.question !== "string") continue;
3147
+ const options = Array.isArray(entry.options) ? entry.options.filter(isRecord2).filter((o) => typeof o.label === "string").map((o) => ({
3148
+ label: o.label,
3149
+ description: typeof o.description === "string" ? o.description : ""
3150
+ })) : [];
3151
+ questions.push({
3152
+ question: entry.question,
3153
+ header: typeof entry.header === "string" ? entry.header : "",
3154
+ options,
3155
+ ...typeof entry.multiSelect === "boolean" ? { multiSelect: entry.multiSelect } : {}
3156
+ });
3157
+ }
3158
+ return questions;
3159
+ }
3110
3160
  var PtySession = class {
3111
3161
  constructor(prompt, options, resume, bridge) {
3112
3162
  this.prompt = prompt;
@@ -3222,6 +3272,21 @@ var PtySession = class {
3222
3272
  } catch {
3223
3273
  }
3224
3274
  }
3275
+ /**
3276
+ * Force the CLI to repaint its full screen by wiggling the pty size
3277
+ * (SIGWINCH). Used after a socket reconnect: the server's scrollback ring is
3278
+ * per-process and starts empty on a new instance, so without a repaint a
3279
+ * quiet TUI would leave the Connected-TUI terminal blank (and hidden) until
3280
+ * the CLI next draws on its own.
3281
+ */
3282
+ forceRepaint() {
3283
+ if (!this.pty) return;
3284
+ try {
3285
+ this.pty.resize(this.cols, Math.max(1, this.rows - 1));
3286
+ this.pty.resize(this.cols, this.rows);
3287
+ } catch {
3288
+ }
3289
+ }
3225
3290
  async teardown() {
3226
3291
  if (this._toreDown) return;
3227
3292
  this._toreDown = true;
@@ -3384,11 +3449,17 @@ var PtySession = class {
3384
3449
  }
3385
3450
  /**
3386
3451
  * PreToolUse request from the hook helper → the runner's `canUseTool`
3387
- * verdict. Only ExitPlanMode is matched by the settings hook, so this is
3388
- * the Conveyor plan-exit gate (validation + identification trigger run
3389
- * inside the callback). Absent callback → allow (mirrors the SDK default).
3452
+ * verdict. ExitPlanMode is the Conveyor plan-exit gate (validation +
3453
+ * identification trigger run inside the callback); AskUserQuestion is
3454
+ * observed and always allowed. Absent callback → allow (mirrors the SDK
3455
+ * default).
3390
3456
  */
3391
3457
  async handlePreToolUse(request) {
3458
+ if (request.tool_name === "AskUserQuestion") {
3459
+ this.disarmSubmitNudge();
3460
+ this.queue.push({ type: "user_question", questions: parseUserQuestions(request.tool_input) });
3461
+ return { decision: "allow" };
3462
+ }
3392
3463
  const canUseTool = this.options.canUseTool;
3393
3464
  let verdict;
3394
3465
  if (canUseTool) {
@@ -3635,6 +3706,17 @@ var PtyHarness = class {
3635
3706
  this.bridge = bridge;
3636
3707
  }
3637
3708
  bridge;
3709
+ /** The session currently streaming events, if any — repaint target. */
3710
+ activeSession = null;
3711
+ /**
3712
+ * Wiggle the live pty's size so the CLI repaints its whole screen. No-op
3713
+ * between queries or on the SDK harness. Called after a socket reconnect so
3714
+ * the server's (per-process, possibly brand-new) scrollback ring re-seeds
3715
+ * and the Connected-TUI terminal reappears.
3716
+ */
3717
+ forceRepaint() {
3718
+ this.activeSession?.forceRepaint();
3719
+ }
3638
3720
  async *executeQuery(opts) {
3639
3721
  const session = new PtySession(
3640
3722
  opts.prompt,
@@ -3645,11 +3727,13 @@ var PtyHarness = class {
3645
3727
  await ensureClaudeCredentials();
3646
3728
  await ensureClaudeOnboarding(process.env, opts.options.cwd);
3647
3729
  await session.start();
3730
+ this.activeSession = session;
3648
3731
  try {
3649
3732
  for await (const event of session.events()) {
3650
3733
  yield event;
3651
3734
  }
3652
3735
  } finally {
3736
+ if (this.activeSession === session) this.activeSession = null;
3653
3737
  await session.teardown();
3654
3738
  }
3655
3739
  }
@@ -5004,7 +5088,7 @@ var cliEventFormatters = {
5004
5088
  tool_result: (e) => `${e.tool} \u2192 ${e.output?.slice(0, 500) ?? ""}${e.isError ? " [ERROR]" : ""}`,
5005
5089
  message: (e) => e.content ?? "",
5006
5090
  error: (e) => `ERROR: ${e.message ?? ""}`,
5007
- completed: (e) => `Completed: ${e.summary ?? ""} (cost: $${e.costUsd ?? "?"}, duration: ${e.durationMs ?? "?"}ms)`,
5091
+ completed: (e) => `Completed: ${e.summary ?? ""} (duration: ${e.durationMs ?? "?"}ms)`,
5008
5092
  setup_output: (e) => `[${e.stream ?? "stdout"}] ${e.data ?? ""}`,
5009
5093
  start_command_output: (e) => `[${e.stream ?? "stdout"}] ${e.data ?? ""}`,
5010
5094
  turn_end: (e) => `Turn complete (${e.toolCalls?.length ?? 0} tool calls)`
@@ -6268,35 +6352,16 @@ function emitContextUpdate(modelUsage, host, context, lastAssistantUsage) {
6268
6352
  });
6269
6353
  }
6270
6354
  }
6271
- function trackCostSpending(host, context, cumulativeTotal) {
6272
- if (cumulativeTotal > 0 && context.agentId && context._runnerSessionId) {
6273
- const breakdown = host.costTracker.modelBreakdown;
6274
- host.connection.trackSpending({
6275
- agentId: context.agentId,
6276
- sessionId: context._runnerSessionId,
6277
- totalCostUsd: cumulativeTotal,
6278
- onSubscription: host.config.mode === "pm" || !!process.env.CLAUDE_CODE_OAUTH_TOKEN,
6279
- modelUsage: breakdown.length > 0 ? breakdown : void 0
6280
- });
6281
- }
6282
- }
6283
6355
  function handleSuccessResult(event, host, context, startTime, lastAssistantUsage) {
6284
6356
  const durationMs = Date.now() - startTime;
6285
6357
  const summary = event.result || "Task completed.";
6286
6358
  const retriable = isRetriableMessage(summary);
6287
- const cumulativeTotal = host.costTracker.addQueryCost(event.total_cost_usd);
6359
+ host.connection.sendEvent({ type: "completed", summary, durationMs });
6288
6360
  const { modelUsage } = event;
6289
- if (modelUsage && typeof modelUsage === "object") {
6290
- host.costTracker.addModelUsage(
6291
- modelUsage
6292
- );
6293
- }
6294
- host.connection.sendEvent({ type: "completed", summary, costUsd: cumulativeTotal, durationMs });
6295
6361
  if (modelUsage && typeof modelUsage === "object") {
6296
6362
  emitContextUpdate(modelUsage, host, context, lastAssistantUsage);
6297
6363
  }
6298
- trackCostSpending(host, context, cumulativeTotal);
6299
- return { totalCostUsd: cumulativeTotal, retriable };
6364
+ return { retriable };
6300
6365
  }
6301
6366
  function handleErrorResult(event, host) {
6302
6367
  const errorMsg = event.errors.length > 0 ? event.errors.join(", ") : `Agent stopped: ${event.subtype}`;
@@ -6325,7 +6390,7 @@ function handleResultEvent(event, host, context, startTime, lastAssistantUsage)
6325
6390
  return { ...result2, resultSummary };
6326
6391
  }
6327
6392
  const result = handleErrorResult(event, host);
6328
- return { totalCostUsd: 0, ...result, resultSummary };
6393
+ return { ...result, resultSummary };
6329
6394
  }
6330
6395
  async function emitResultEvent(event, host, context, startTime, lastAssistantUsage) {
6331
6396
  const result = handleResultEvent(event, host, context, startTime, lastAssistantUsage);
@@ -6336,7 +6401,6 @@ async function emitResultEvent(event, host, context, startTime, lastAssistantUsa
6336
6401
  await host.callbacks.onEvent({
6337
6402
  type: "completed",
6338
6403
  summary,
6339
- costUsd: result.totalCostUsd,
6340
6404
  durationMs
6341
6405
  });
6342
6406
  } else if (!result.staleSession) {
@@ -6456,6 +6520,44 @@ async function handleResultCase(event, host, context, startTime, isTyping, lastA
6456
6520
  }
6457
6521
 
6458
6522
  // src/execution/event-processor.ts
6523
+ function hasAskUserQuestionBlock(event) {
6524
+ return event.message.content.some((b) => b.type === "tool_use" && b.name === "AskUserQuestion");
6525
+ }
6526
+ async function armQuestionPending(host, state) {
6527
+ state.questionPending = true;
6528
+ await host.connection.emitStatus("waiting_for_input", AGENT_STATUS_REASON_USER_QUESTION);
6529
+ await host.callbacks.onStatusChange("waiting_for_input");
6530
+ }
6531
+ async function clearQuestionPending(host, state, options) {
6532
+ if (!state.questionPending) return;
6533
+ state.questionPending = false;
6534
+ if (options.emitRunning) {
6535
+ await host.connection.emitStatus("running");
6536
+ await host.callbacks.onStatusChange("running");
6537
+ }
6538
+ }
6539
+ async function applyQuestionTransitions(event, host, state) {
6540
+ switch (event.type) {
6541
+ case "user_question":
6542
+ await armQuestionPending(host, state);
6543
+ return;
6544
+ case "assistant":
6545
+ if (!hasAskUserQuestionBlock(event)) {
6546
+ await clearQuestionPending(host, state, { emitRunning: true });
6547
+ }
6548
+ return;
6549
+ case "tool_progress":
6550
+ if (event.tool_name === "AskUserQuestion") {
6551
+ await clearQuestionPending(host, state, { emitRunning: true });
6552
+ }
6553
+ return;
6554
+ case "result":
6555
+ await clearQuestionPending(host, state, { emitRunning: false });
6556
+ return;
6557
+ default:
6558
+ break;
6559
+ }
6560
+ }
6459
6561
  function stopTypingIfNeeded(host, isTyping) {
6460
6562
  if (isTyping) host.connection.sendTypingStop();
6461
6563
  }
@@ -6535,13 +6637,14 @@ async function processEvents(events, context, host) {
6535
6637
  staleSession: void 0,
6536
6638
  authError: void 0,
6537
6639
  lastAssistantUsage: void 0,
6538
- turnToolCalls: []
6640
+ turnToolCalls: [],
6641
+ questionPending: false
6539
6642
  };
6540
6643
  for await (const event of events) {
6541
6644
  if (host.isStopped()) break;
6542
6645
  flushPendingToolCalls(host, state.turnToolCalls);
6543
6646
  const now = Date.now();
6544
- if (now - lastStatusEmit >= STATUS_REEMIT_INTERVAL_MS) {
6647
+ if (now - lastStatusEmit >= STATUS_REEMIT_INTERVAL_MS && !state.questionPending) {
6545
6648
  host.connection.emitStatus("running");
6546
6649
  lastStatusEmit = now;
6547
6650
  }
@@ -6549,6 +6652,7 @@ async function processEvents(events, context, host) {
6549
6652
  stopTypingIfNeeded(host, state.isTyping);
6550
6653
  return { retriable: false, modeRestart: true };
6551
6654
  }
6655
+ await applyQuestionTransitions(event, host, state);
6552
6656
  switch (event.type) {
6553
6657
  case "system":
6554
6658
  await processSystemCase(event, host, context, state);
@@ -7051,7 +7155,6 @@ function buildQueryOptions(host, context) {
7051
7155
  effort: settings.effort,
7052
7156
  thinking: settings.thinking,
7053
7157
  betas: settings.betas,
7054
- maxBudgetUsd: settings.maxBudgetUsd ?? 50,
7055
7158
  abortController: host.abortController ?? void 0,
7056
7159
  disallowedTools: buildDisallowedTools(settings, mode, host.hasExitedPlanMode),
7057
7160
  enableFileCheckpointing: settings.enableFileCheckpointing,
@@ -7450,58 +7553,6 @@ async function runWithRetry(initialQuery, context, host, options) {
7450
7553
  }
7451
7554
  }
7452
7555
 
7453
- // src/execution/cost-tracker.ts
7454
- var CostTracker = class {
7455
- cumulativeCostUsd = 0;
7456
- modelUsage = /* @__PURE__ */ new Map();
7457
- seeded = false;
7458
- /**
7459
- * Rehydrate cumulative spend from the server so `maxBudgetUsd` enforcement
7460
- * accounts for costs incurred by prior agent runs on the same task. Must be
7461
- * called before any `addQueryCost` / `addModelUsage` — re-seeding after
7462
- * costs have accumulated would clobber in-process totals.
7463
- */
7464
- seed(totalCostUsd, modelUsage) {
7465
- if (this.seeded) return;
7466
- if (this.cumulativeCostUsd > 0 || this.modelUsage.size > 0) return;
7467
- this.cumulativeCostUsd = totalCostUsd;
7468
- for (const entry of modelUsage) {
7469
- this.modelUsage.set(entry.model, { ...entry });
7470
- }
7471
- this.seeded = true;
7472
- }
7473
- /** Add cost from a completed query and return the running total */
7474
- addQueryCost(queryCostUsd) {
7475
- this.cumulativeCostUsd += queryCostUsd;
7476
- return this.cumulativeCostUsd;
7477
- }
7478
- /** Merge per-model usage from a completed query */
7479
- addModelUsage(usage) {
7480
- for (const [model, data] of Object.entries(usage)) {
7481
- const existing = this.modelUsage.get(model) ?? {
7482
- model,
7483
- inputTokens: 0,
7484
- outputTokens: 0,
7485
- cacheReadInputTokens: 0,
7486
- cacheCreationInputTokens: 0,
7487
- costUSD: 0
7488
- };
7489
- existing.inputTokens += data.inputTokens ?? 0;
7490
- existing.outputTokens += data.outputTokens ?? 0;
7491
- existing.cacheReadInputTokens += data.cacheReadInputTokens ?? 0;
7492
- existing.cacheCreationInputTokens += data.cacheCreationInputTokens ?? 0;
7493
- existing.costUSD += data.costUSD ?? 0;
7494
- this.modelUsage.set(model, existing);
7495
- }
7496
- }
7497
- get totalCostUsd() {
7498
- return this.cumulativeCostUsd;
7499
- }
7500
- get modelBreakdown() {
7501
- return [...this.modelUsage.values()];
7502
- }
7503
- };
7504
-
7505
7556
  // src/execution/exploration-tracker.ts
7506
7557
  var FILE_REREAD_NUDGE_L1 = 3;
7507
7558
  var FILE_REREAD_NUDGE_L2 = 5;
@@ -7628,7 +7679,6 @@ var QueryBridge = class {
7628
7679
  harnessKind,
7629
7680
  harnessKind === "pty" ? buildPtyBridge(connection) : void 0
7630
7681
  );
7631
- this.costTracker = new CostTracker();
7632
7682
  this.planSync = new PlanSync(workspaceDir, connection);
7633
7683
  }
7634
7684
  connection;
@@ -7639,7 +7689,6 @@ var QueryBridge = class {
7639
7689
  /** Which harness drives this bridge ("pty" task chat; "sdk" only under the
7640
7690
  * CONVEYOR_FORCE_SDK_CARDS maintainer override). */
7641
7691
  harnessKind;
7642
- costTracker;
7643
7692
  planSync;
7644
7693
  sessionIds = /* @__PURE__ */ new Map();
7645
7694
  activeQuery = null;
@@ -7671,6 +7720,13 @@ var QueryBridge = class {
7671
7720
  get wasRateLimited() {
7672
7721
  return this._wasRateLimited;
7673
7722
  }
7723
+ /**
7724
+ * Ask the live terminal (PTY harness only) to redraw its full screen.
7725
+ * Safe no-op on the SDK harness or between queries.
7726
+ */
7727
+ forceRepaint() {
7728
+ this.harness.forceRepaint?.();
7729
+ }
7674
7730
  stop() {
7675
7731
  this._stopped = true;
7676
7732
  this._abortController?.abort();
@@ -7678,10 +7734,6 @@ var QueryBridge = class {
7678
7734
  resume() {
7679
7735
  this._stopped = false;
7680
7736
  }
7681
- /** Rehydrate CostTracker from server-side cumulative spend on agent boot. */
7682
- seedCostTracker(totalCostUsd, modelUsage) {
7683
- this.costTracker.seed(totalCostUsd, modelUsage);
7684
- }
7685
7737
  /**
7686
7738
  * How the INITIAL instructions for this task would be delivered — "prefill"
7687
7739
  * means the TUI input is pre-filled but unsubmitted, awaiting the human.
@@ -7740,7 +7792,6 @@ var QueryBridge = class {
7740
7792
  harness: this.harness,
7741
7793
  harnessKind: this.harnessKind,
7742
7794
  setupLog: [],
7743
- costTracker: this.costTracker,
7744
7795
  explorationTracker: bridge.mode.effectiveMode === "discovery" || bridge.mode.effectiveMode === "auto" && !bridge.mode.hasExitedPlanMode ? new ExplorationTracker(bridge._isParentTask) : null,
7745
7796
  sessionIds: this.sessionIds,
7746
7797
  pendingToolOutputs: this.pendingToolOutputs,
@@ -8050,7 +8101,6 @@ var SessionRunner = class _SessionRunner {
8050
8101
  });
8051
8102
  }
8052
8103
  this.queryBridge = this.createQueryBridge();
8053
- await this.seedCostTrackerFromServer();
8054
8104
  this.logInitialization();
8055
8105
  const staleBatch = [...this.pendingMessages];
8056
8106
  const didExecuteInitialQuery = await this.executeInitialMode();
@@ -8602,6 +8652,7 @@ var SessionRunner = class _SessionRunner {
8602
8652
  this.connection.onMessage((msg) => this.injectMessage(msg));
8603
8653
  this.connection.onStop(() => this.stop());
8604
8654
  this.connection.onSoftStop(() => this.softStop());
8655
+ this.connection.onReconnected = () => this.queryBridge?.forceRepaint();
8605
8656
  this.connection.onModeChange((data) => {
8606
8657
  const action = this.mode.handleModeChange(data.agentMode);
8607
8658
  if (action.type === "start_auto") {
@@ -8634,36 +8685,6 @@ var SessionRunner = class _SessionRunner {
8634
8685
  handlePullBranch(this.config.workspaceDir, branch);
8635
8686
  });
8636
8687
  }
8637
- /**
8638
- * Rehydrate CostTracker from server. Caps at 3s so a slow API call never
8639
- * blocks agent startup — on timeout/error we fall through with a $0 tracker
8640
- * (the existing behavior before this rehydration path was added).
8641
- */
8642
- async seedCostTrackerFromServer() {
8643
- if (!this.queryBridge) return;
8644
- const TIMEOUT_MS = 3e3;
8645
- try {
8646
- const timeout = new Promise((resolve) => {
8647
- setTimeout(() => resolve(null), TIMEOUT_MS);
8648
- });
8649
- const fetched = await Promise.race([this.connection.getCumulativeSpending(), timeout]);
8650
- if (fetched) {
8651
- this.queryBridge.seedCostTracker(fetched.totalCostUsd, fetched.modelUsage);
8652
- process.stderr.write(
8653
- `[conveyor-agent] CostTracker seeded: $${fetched.totalCostUsd.toFixed(4)} across ${fetched.modelUsage.length} model(s)
8654
- `
8655
- );
8656
- } else {
8657
- process.stderr.write(
8658
- "[conveyor-agent] CostTracker seed timed out after 3s \u2014 starting at $0\n"
8659
- );
8660
- }
8661
- } catch (err) {
8662
- const msg = err instanceof Error ? err.message : String(err);
8663
- process.stderr.write(`[conveyor-agent] CostTracker seed failed: ${msg} \u2014 starting at $0
8664
- `);
8665
- }
8666
- }
8667
8688
  /** Proactively refresh the GitHub token before the 1-hour expiry. */
8668
8689
  async refreshGithubToken() {
8669
8690
  try {
@@ -8893,4 +8914,4 @@ export {
8893
8914
  runStartCommand,
8894
8915
  unshallowRepo
8895
8916
  };
8896
- //# sourceMappingURL=chunk-EYIGNRRW.js.map
8917
+ //# sourceMappingURL=chunk-34NA4BCK.js.map