@rallycry/conveyor-agent 10.13.65 → 10.13.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/{boot-7OPX55AO.js → boot-UUPTBQ6R.js} +7 -8
  2. package/dist/{chunk-JIGG755T.js → chunk-6Q6LQBWO.js} +0 -1
  3. package/dist/{chunk-ULS4QPRE.js → chunk-6W6UZ4SJ.js} +0 -1
  4. package/dist/{chunk-AXA55U4Z.js → chunk-E2SHIH6Y.js} +3 -4
  5. package/dist/{chunk-QOJTJCYZ.js → chunk-EXQ6AHOY.js} +2 -3
  6. package/dist/{chunk-5OQQSDVT.js → chunk-IA45XHOA.js} +0 -1
  7. package/dist/{chunk-QZN6HVUY.js → chunk-KG4ORL3Y.js} +1 -2
  8. package/dist/{chunk-4VUQ2NPF.js → chunk-KMB3BU4S.js} +0 -1
  9. package/dist/{chunk-NAK6FY5U.js → chunk-LE6ZUDZT.js} +37 -6
  10. package/dist/{chunk-6GS5ADIY.js → chunk-R3FDJQL6.js} +0 -1
  11. package/dist/{chunk-BAQ2OAKB.js → chunk-XPZPR6NS.js} +591 -216
  12. package/dist/cli.js +35 -43
  13. package/dist/{client-LRVVHTNG.js → client-IG6C5F2G.js} +3 -4
  14. package/dist/heartbeat-worker.js +1 -2
  15. package/dist/index.d.ts +95 -13
  16. package/dist/index.js +7 -8
  17. package/dist/{mode-6RL3SVMV.js → mode-ZJSOSLGU.js} +1 -2
  18. package/dist/{oom-watchdog-U7JERHA2.js → oom-watchdog-PAC5OJJG.js} +1 -2
  19. package/dist/{protocol-QLVS5W6O.js → protocol-QBCYO4GI.js} +1 -2
  20. package/dist/server-US2DDQSW.js +9 -0
  21. package/package.json +2 -2
  22. package/dist/boot-7OPX55AO.js.map +0 -1
  23. package/dist/chunk-4VUQ2NPF.js.map +0 -1
  24. package/dist/chunk-5OQQSDVT.js.map +0 -1
  25. package/dist/chunk-6GS5ADIY.js.map +0 -1
  26. package/dist/chunk-AXA55U4Z.js.map +0 -1
  27. package/dist/chunk-BAQ2OAKB.js.map +0 -1
  28. package/dist/chunk-JIGG755T.js.map +0 -1
  29. package/dist/chunk-NAK6FY5U.js.map +0 -1
  30. package/dist/chunk-QOJTJCYZ.js.map +0 -1
  31. package/dist/chunk-QZN6HVUY.js.map +0 -1
  32. package/dist/chunk-ULS4QPRE.js.map +0 -1
  33. package/dist/cli.js.map +0 -1
  34. package/dist/client-LRVVHTNG.js.map +0 -1
  35. package/dist/heartbeat-worker.js.map +0 -1
  36. package/dist/index.js.map +0 -1
  37. package/dist/mode-6RL3SVMV.js.map +0 -1
  38. package/dist/oom-watchdog-U7JERHA2.js.map +0 -1
  39. package/dist/protocol-QLVS5W6O.js.map +0 -1
  40. package/dist/server-ZACB5T5S.js +0 -10
  41. package/dist/server-ZACB5T5S.js.map +0 -1
@@ -7,7 +7,7 @@ import {
7
7
  readAgentVersion,
8
8
  registerBootMilestoneSocketFallback,
9
9
  reportBootMilestone
10
- } from "./chunk-QZN6HVUY.js";
10
+ } from "./chunk-KG4ORL3Y.js";
11
11
  import {
12
12
  LoopLagMonitor,
13
13
  buildConveyorSocketOptions,
@@ -15,14 +15,14 @@ import {
15
15
  heartbeatStatusFor,
16
16
  loopStatusForRunnerStatus,
17
17
  waitForConnected
18
- } from "./chunk-5OQQSDVT.js";
18
+ } from "./chunk-IA45XHOA.js";
19
19
  import {
20
20
  WorkbenchError,
21
21
  getWorkbenchClient
22
- } from "./chunk-QOJTJCYZ.js";
22
+ } from "./chunk-EXQ6AHOY.js";
23
23
  import {
24
24
  workbenchEnabled
25
- } from "./chunk-4VUQ2NPF.js";
25
+ } from "./chunk-KMB3BU4S.js";
26
26
  import {
27
27
  MAX_BETWEEN_TURN_BUFFER,
28
28
  MAX_DIAGNOSTIC_OUTPUT,
@@ -30,6 +30,7 @@ import {
30
30
  buildPromptBytes,
31
31
  buildSpawnArgs,
32
32
  cleanTerminalOutput,
33
+ describeTokenFile,
33
34
  ghHostsExternallyOwned,
34
35
  gitCredentialHelper,
35
36
  githubTokenFilePath,
@@ -54,7 +55,7 @@ import {
54
55
  transcriptSize,
55
56
  turnOptionsFrom,
56
57
  writeGitCredential
57
- } from "./chunk-NAK6FY5U.js";
58
+ } from "./chunk-LE6ZUDZT.js";
58
59
 
59
60
  // src/setup/bootstrap.ts
60
61
  var BOOTSTRAP_TIMEOUT_MS = 3e4;
@@ -73,11 +74,11 @@ async function singleBootstrapAttempt(apiUrl, instanceName, bootstrapToken, time
73
74
  signal: controller.signal
74
75
  });
75
76
  if (!response.ok) {
76
- const errorText = await response.text().catch(() => "");
77
+ const errorText2 = await response.text().catch(() => "");
77
78
  return {
78
79
  ok: false,
79
80
  status: response.status,
80
- errorText: errorText.slice(0, 500),
81
+ errorText: errorText2.slice(0, 500),
81
82
  reason: response.status === 401 || response.status === 403 ? "auth_rejected" : "http_error"
82
83
  };
83
84
  }
@@ -167,6 +168,28 @@ function applyBootstrapToEnv(config) {
167
168
  if (config.taskBranch) process.env.CONVEYOR_TASK_BRANCH = config.taskBranch;
168
169
  }
169
170
 
171
+ // src/utils/logger.ts
172
+ function createServiceLogger(service) {
173
+ const prefix = `[conveyor-agent:${service}]`;
174
+ return {
175
+ info(message, data) {
176
+ const extra = data ? ` ${JSON.stringify(data)}` : "";
177
+ process.stderr.write(`${prefix} ${message}${extra}
178
+ `);
179
+ },
180
+ warn(message, data) {
181
+ const extra = data ? ` ${JSON.stringify(data)}` : "";
182
+ process.stderr.write(`${prefix} WARN ${message}${extra}
183
+ `);
184
+ },
185
+ error(message, data) {
186
+ const extra = data ? ` ${JSON.stringify(data)}` : "";
187
+ process.stderr.write(`${prefix} ERROR ${message}${extra}
188
+ `);
189
+ }
190
+ };
191
+ }
192
+
170
193
  // src/connection/agent-connection.ts
171
194
  import { existsSync } from "fs";
172
195
  import { fileURLToPath } from "url";
@@ -204,7 +227,53 @@ async function pollUntilBound(opts) {
204
227
  }
205
228
  }
206
229
 
230
+ // src/connection/bundle-credentials.ts
231
+ function readBundleIdentity(sessionJwt) {
232
+ if (!sessionJwt) return {};
233
+ const segments = sessionJwt.split(".");
234
+ if (segments.length !== 3) return {};
235
+ try {
236
+ const json = Buffer.from(segments[1], "base64url").toString("utf8");
237
+ const claims = JSON.parse(json);
238
+ return {
239
+ ...typeof claims.sessionId === "string" ? { sessionId: claims.sessionId } : {},
240
+ ...typeof claims.role === "string" ? { role: claims.role } : {}
241
+ };
242
+ } catch {
243
+ return {};
244
+ }
245
+ }
246
+ function bundleMayWriteGithubFiles(bundle, self) {
247
+ const identity = readBundleIdentity(bundle.sessionJwt);
248
+ if (identity.sessionId && self.sessionId && identity.sessionId !== self.sessionId) {
249
+ return {
250
+ allowed: false,
251
+ reason: `bundle resolved to session ${identity.sessionId}, not ours (${self.sessionId})`
252
+ };
253
+ }
254
+ if (identity.role === "reader" && self.role && self.role !== "reader") {
255
+ return {
256
+ allowed: false,
257
+ reason: `bundle carries a reader-scoped token but this session is a ${self.role}`
258
+ };
259
+ }
260
+ return { allowed: true };
261
+ }
262
+ function applyBundleGithubToken(bundle, self) {
263
+ if (!bundle.githubToken) return { written: false, reason: "bundle carried no GitHub token" };
264
+ const permitted = bundleMayWriteGithubFiles(bundle, self);
265
+ if (!permitted.allowed) {
266
+ return { written: false, ...permitted.reason ? { reason: permitted.reason } : {} };
267
+ }
268
+ syncGithubTokenFiles(bundle.githubToken);
269
+ return { written: true };
270
+ }
271
+ function syncBundleGithubToken(token) {
272
+ syncGithubTokenFiles(token);
273
+ }
274
+
207
275
  // src/connection/agent-connection.ts
276
+ var logger = createServiceLogger("agent-connection");
208
277
  var EVENT_BATCH_MS = 500;
209
278
  var MAX_EVENT_BUFFER = 5e3;
210
279
  var TOKEN_REFRESH_INTERVAL_MS = 45 * 60 * 1e3;
@@ -237,6 +306,7 @@ var AgentConnection = class _AgentConnection {
237
306
  apiKeyUpdateCallback = null;
238
307
  pullBranchCallback = null;
239
308
  runStartCommandCallback = null;
309
+ earlyRunStartCommand = false;
240
310
  earlyPullBranches = [];
241
311
  spawnReviewCallback = null;
242
312
  earlySpawnReviews = [];
@@ -426,7 +496,8 @@ var AgentConnection = class _AgentConnection {
426
496
  else this.earlyProbeUsage = true;
427
497
  });
428
498
  this.socket.on("session:runStartCommand", () => {
429
- this.runStartCommandCallback?.();
499
+ if (this.runStartCommandCallback) this.runStartCommandCallback();
500
+ else this.earlyRunStartCommand = true;
430
501
  });
431
502
  this.socket.on("pty:input", (data) => {
432
503
  if (data.sessionId && data.sessionId !== this.config.sessionId) return;
@@ -728,6 +799,26 @@ var AgentConnection = class _AgentConnection {
728
799
  }).catch(() => {
729
800
  });
730
801
  }
802
+ /**
803
+ * Report that this pod's git credential is dead and refreshing did not fix
804
+ * it (fire-and-forget).
805
+ *
806
+ * Purely diagnostic. Until this existed a pod could lose git entirely and
807
+ * leave no server-side trace at all — the refresh RPC succeeded every time,
808
+ * so the failure was visible only in pod stderr, which is why it took two
809
+ * investigations to attribute. `tokenShape` describes the served credential
810
+ * (length, prefix class, mtime) and NEVER carries its value.
811
+ */
812
+ reportCredentialFailure(details) {
813
+ if (!this.socket) return;
814
+ void this.call("reportCredentialFailure", {
815
+ sessionId: this.config.sessionId,
816
+ ...details.error ? { error: details.error.slice(0, 2e3) } : {},
817
+ ...details.tokenShape ? { tokenShape: details.tokenShape.slice(0, 500) } : {},
818
+ ...details.healed === void 0 ? {} : { healed: details.healed }
819
+ }).catch(() => {
820
+ });
821
+ }
731
822
  /**
732
823
  * Ask the server to destroy and recreate this pod (fire-and-forget). The
733
824
  * agent calls this only when it has proven it cannot recover in place — the
@@ -772,8 +863,15 @@ var AgentConnection = class _AgentConnection {
772
863
  }).catch(() => {
773
864
  });
774
865
  }
866
+ /** Register the restart handler, draining a `session:runStartCommand` that
867
+ * arrived before the supervisor was ready. Collapsed to one drain: two
868
+ * clicks during boot should produce one restart, not two competing ones. */
775
869
  onRunStartCommand(callback) {
776
870
  this.runStartCommandCallback = callback;
871
+ if (this.earlyRunStartCommand) {
872
+ this.earlyRunStartCommand = false;
873
+ callback();
874
+ }
777
875
  }
778
876
  // ── PTY relay (S5 Connected-TUI terminal) ──────────────────────────
779
877
  /**
@@ -1159,11 +1257,20 @@ ${q.question}${q.options.length ? "\n" + q.options.map((o) => `- ${o.label}: ${o
1159
1257
  // means the token is consumed / session terminal and retrying won't
1160
1258
  // help. Network/timeout still retry inside fetchBootstrap.
1161
1259
  });
1162
- if (!result.ok) return { refreshedClaude: false, refreshedTaskToken: false };
1260
+ if (!result.ok) {
1261
+ logger.warn("bootstrap refresh abandoned \u2014 pod credentials will go stale", {
1262
+ path: "codespace",
1263
+ reason: result.reason,
1264
+ status: result.status,
1265
+ attempts: result.attempts,
1266
+ detail: result.detail
1267
+ });
1268
+ return { refreshedClaude: false, refreshedTaskToken: false };
1269
+ }
1163
1270
  const previousTaskToken = process.env.CONVEYOR_TASK_TOKEN;
1164
1271
  applyBootstrapToEnv(result.config);
1165
1272
  const env = result.config.envVars ?? {};
1166
- syncGithubTokenFiles(env.CONVEYOR_GITHUB_TOKEN ?? env.GH_TOKEN ?? env.GITHUB_TOKEN);
1273
+ syncBundleGithubToken(env.CONVEYOR_GITHUB_TOKEN ?? env.GH_TOKEN ?? env.GITHUB_TOKEN);
1167
1274
  const refreshedTaskToken = result.config.mode !== "project" && Boolean(result.config.taskToken) && result.config.taskToken !== previousTaskToken;
1168
1275
  if (refreshedTaskToken && result.config.taskToken) {
1169
1276
  this.config.taskToken = result.config.taskToken;
@@ -1195,7 +1302,7 @@ ${q.question}${q.options.length ? "\n" + q.options.map((o) => `- ${o.label}: ${o
1195
1302
  }
1196
1303
  if (bundle.githubToken) {
1197
1304
  process.env.CONVEYOR_GITHUB_TOKEN = bundle.githubToken;
1198
- syncGithubTokenFiles(bundle.githubToken);
1305
+ this.applyBundleCredentialFiles(bundle, previousTaskToken);
1199
1306
  }
1200
1307
  if (bundle.anthropicKey) process.env.ANTHROPIC_API_KEY = bundle.anthropicKey;
1201
1308
  if (bundle.gcpToken) process.env.CLOUDSDK_AUTH_ACCESS_TOKEN = bundle.gcpToken;
@@ -1214,6 +1321,32 @@ ${q.question}${q.options.length ? "\n" + q.options.map((o) => `- ${o.label}: ${o
1214
1321
  const refreshedClaude = Boolean(bundle.envVars?.CLAUDE_CODE_OAUTH_TOKEN);
1215
1322
  return { refreshedClaude, refreshedTaskToken };
1216
1323
  }
1324
+ /**
1325
+ * Write the bundle's GitHub token to the shared credential files — unless the
1326
+ * bundle belongs to another session.
1327
+ *
1328
+ * The bootstrap GET is keyed by the POD, so on a pod also hosting a same-pod
1329
+ * review it can resolve to the reader session, whose token is read-only.
1330
+ * Writing that over the shared files silently downgrades the builder's push
1331
+ * credential. Our own taskToken carries the same claims, so it is what we
1332
+ * compare against.
1333
+ *
1334
+ * The legitimate case this path exists for — our own session's bundle
1335
+ * refreshing the token when the RPC is failing — is unaffected.
1336
+ */
1337
+ applyBundleCredentialFiles(bundle, previousTaskToken) {
1338
+ const self = readBundleIdentity(previousTaskToken);
1339
+ const result = applyBundleGithubToken(bundle, {
1340
+ sessionId: this.config.sessionId || self.sessionId,
1341
+ ...self.role ? { role: self.role } : {}
1342
+ });
1343
+ if (!result.written && result.reason) {
1344
+ process.stderr.write(
1345
+ `[conveyor-agent] Skipped writing GitHub credential files from the bootstrap bundle: ${result.reason}
1346
+ `
1347
+ );
1348
+ }
1349
+ }
1217
1350
  /**
1218
1351
  * Poll the bootstrap bundle once (maxWaitMs 0), retrying only on a transient
1219
1352
  * 429 (standby-pool pods share one Cloud NAT IP against podBootstrapLimiter)
@@ -1229,6 +1362,12 @@ ${q.question}${q.options.length ? "\n" + q.options.map((o) => `- ${o.label}: ${o
1229
1362
  } catch (err) {
1230
1363
  const isRateLimited = err instanceof PollUntilBoundHttpError && err.status === 429;
1231
1364
  if (!isRateLimited || attempt >= retryDelaysMs.length) {
1365
+ logger.warn("bootstrap refresh abandoned \u2014 pod credentials will go stale", {
1366
+ path: "v3",
1367
+ attempt,
1368
+ rateLimited: isRateLimited,
1369
+ error: err instanceof Error ? err.message : String(err)
1370
+ });
1232
1371
  return null;
1233
1372
  }
1234
1373
  await new Promise((resolve) => {
@@ -1290,31 +1429,8 @@ function isPermissionDeniedError(err) {
1290
1429
  return /insufficient permissions|authentication required/i.test(message);
1291
1430
  }
1292
1431
 
1293
- // src/utils/logger.ts
1294
- function createServiceLogger(service) {
1295
- const prefix = `[conveyor-agent:${service}]`;
1296
- return {
1297
- info(message, data) {
1298
- const extra = data ? ` ${JSON.stringify(data)}` : "";
1299
- process.stderr.write(`${prefix} ${message}${extra}
1300
- `);
1301
- },
1302
- warn(message, data) {
1303
- const extra = data ? ` ${JSON.stringify(data)}` : "";
1304
- process.stderr.write(`${prefix} WARN ${message}${extra}
1305
- `);
1306
- },
1307
- error(message, data) {
1308
- const extra = data ? ` ${JSON.stringify(data)}` : "";
1309
- process.stderr.write(`${prefix} ERROR ${message}${extra}
1310
- `);
1311
- }
1312
- };
1313
- }
1314
-
1315
- // src/runner/git-utils.ts
1432
+ // src/runner/git-run.ts
1316
1433
  import { execFile } from "child_process";
1317
- import { realpathSync } from "fs";
1318
1434
  import { promisify } from "util";
1319
1435
  var execFileAsync = promisify(execFile);
1320
1436
  var GIT_TIMEOUT_MS = 6e4;
@@ -1322,12 +1438,19 @@ var GIT_SLOW_TIMEOUT_MS = 12e4;
1322
1438
  var GIT_MAX_BUFFER = 16 * 1024 * 1024;
1323
1439
  async function git(cwd, args, timeoutMs = GIT_TIMEOUT_MS) {
1324
1440
  if (workbenchEnabled()) {
1325
- const { stdout: stdout2 } = await getWorkbenchClient().execFile("git", args, {
1326
- cwd,
1327
- timeout: timeoutMs,
1328
- maxBuffer: GIT_MAX_BUFFER
1329
- });
1330
- return stdout2.trim();
1441
+ try {
1442
+ const { stdout: stdout2 } = await getWorkbenchClient().execFile("git", args, {
1443
+ cwd,
1444
+ timeout: timeoutMs,
1445
+ maxBuffer: GIT_MAX_BUFFER
1446
+ });
1447
+ return stdout2.trim();
1448
+ } catch (err) {
1449
+ if (err instanceof Error && err.message.startsWith("Command timed out:")) {
1450
+ err.killed = true;
1451
+ }
1452
+ throw err;
1453
+ }
1331
1454
  }
1332
1455
  const { stdout } = await execFileAsync("git", args, {
1333
1456
  cwd,
@@ -1336,6 +1459,54 @@ async function git(cwd, args, timeoutMs = GIT_TIMEOUT_MS) {
1336
1459
  });
1337
1460
  return stdout.toString().trim();
1338
1461
  }
1462
+
1463
+ // src/runner/git-credential-ops.ts
1464
+ function credentialErrorText(err) {
1465
+ const raw = err instanceof Error ? err.message : String(err);
1466
+ return raw.replace(/\/\/[^@\s]+@/g, "//***@");
1467
+ }
1468
+ async function updateRemoteCredential(cwd, credential) {
1469
+ const result = { ok: false, storeWritten: false, helperConfigured: false };
1470
+ try {
1471
+ const currentUrl = await git(cwd, ["remote", "get-url", "origin"]);
1472
+ const cloneUrl = credential.cloneUrl ?? currentUrl;
1473
+ const normalizedUrl = writeGitCredential(cwd, cloneUrl, credential);
1474
+ result.storeWritten = true;
1475
+ if (currentUrl !== normalizedUrl) {
1476
+ await git(cwd, ["remote", "set-url", "origin", normalizedUrl]);
1477
+ }
1478
+ await git(cwd, ["config", "--local", "credential.helper", gitCredentialHelper(cwd)]);
1479
+ result.helperConfigured = true;
1480
+ result.ok = true;
1481
+ } catch (err) {
1482
+ result.error = credentialErrorText(err);
1483
+ }
1484
+ return result;
1485
+ }
1486
+ async function updateRemoteToken(cwd, token) {
1487
+ const username = process.env.CONVEYOR_GIT_USERNAME || "x-access-token";
1488
+ const cloneUrl = process.env.CONVEYOR_GIT_CLONE_URL || void 0;
1489
+ const credential = await updateRemoteCredential(cwd, { username, secret: token, cloneUrl });
1490
+ process.env.CONVEYOR_GIT_SECRET = token;
1491
+ const files = syncGithubTokenFiles(token);
1492
+ return { credential, files };
1493
+ }
1494
+ async function verifyGitCredential(cwd) {
1495
+ try {
1496
+ await git(cwd, ["ls-remote", "--heads", "origin"], 3e4);
1497
+ return { ok: true, outcome: "ok" };
1498
+ } catch (err) {
1499
+ const killed = err.killed === true;
1500
+ return {
1501
+ ok: false,
1502
+ outcome: killed ? "timeout" : "denied",
1503
+ error: credentialErrorText(err)
1504
+ };
1505
+ }
1506
+ }
1507
+
1508
+ // src/runner/git-utils.ts
1509
+ import { realpathSync } from "fs";
1339
1510
  async function ensureOnTaskBranch(cwd, taskBranch, baseBranch) {
1340
1511
  if (!taskBranch) return true;
1341
1512
  try {
@@ -1464,44 +1635,6 @@ async function isAuthError(cwd) {
1464
1635
  return errLooksLikeAuth(err);
1465
1636
  }
1466
1637
  }
1467
- function credentialErrorText(err) {
1468
- const raw = err instanceof Error ? err.message : String(err);
1469
- return raw.replace(/\/\/[^@\s]+@/g, "//***@");
1470
- }
1471
- async function updateRemoteCredential(cwd, credential) {
1472
- const result = { ok: false, storeWritten: false, helperConfigured: false };
1473
- try {
1474
- const currentUrl = await git(cwd, ["remote", "get-url", "origin"]);
1475
- const cloneUrl = credential.cloneUrl ?? currentUrl;
1476
- const normalizedUrl = writeGitCredential(cwd, cloneUrl, credential);
1477
- result.storeWritten = true;
1478
- if (currentUrl !== normalizedUrl) {
1479
- await git(cwd, ["remote", "set-url", "origin", normalizedUrl]);
1480
- }
1481
- await git(cwd, ["config", "--local", "credential.helper", gitCredentialHelper(cwd)]);
1482
- result.helperConfigured = true;
1483
- result.ok = true;
1484
- } catch (err) {
1485
- result.error = credentialErrorText(err);
1486
- }
1487
- return result;
1488
- }
1489
- async function updateRemoteToken(cwd, token) {
1490
- const username = process.env.CONVEYOR_GIT_USERNAME || "x-access-token";
1491
- const cloneUrl = process.env.CONVEYOR_GIT_CLONE_URL || void 0;
1492
- const credential = await updateRemoteCredential(cwd, { username, secret: token, cloneUrl });
1493
- process.env.CONVEYOR_GIT_SECRET = token;
1494
- const files = syncGithubTokenFiles(token);
1495
- return { credential, files };
1496
- }
1497
- async function verifyGitCredential(cwd) {
1498
- try {
1499
- await git(cwd, ["ls-remote", "--heads", "origin"], 3e4);
1500
- return { ok: true };
1501
- } catch (err) {
1502
- return { ok: false, error: credentialErrorText(err) };
1503
- }
1504
- }
1505
1638
  function wipRefForBranch(branch) {
1506
1639
  return `conveyor-wip/${branch}`;
1507
1640
  }
@@ -1645,7 +1778,7 @@ async function pushToOrigin(cwd, refreshToken, skipVerify = false) {
1645
1778
  }
1646
1779
  if (await tryPush(cwd, currentBranch, skipVerify)) return true;
1647
1780
  if (refreshToken && await isAuthError(cwd)) {
1648
- const token = await refreshToken();
1781
+ const token = await refreshToken({ forceFresh: true });
1649
1782
  if (token) {
1650
1783
  await updateRemoteToken(cwd, token);
1651
1784
  process.env.GITHUB_TOKEN = token;
@@ -2021,16 +2154,19 @@ var KnownAgentEventSchema = z3.discriminatedUnion("type", [
2021
2154
  cacheCreationInputTokens: z3.number().optional(),
2022
2155
  totalTokensUsed: z3.number().optional()
2023
2156
  }).passthrough(),
2024
- // Three producer shapes share this type: {rateLimitType, utilization, status}
2025
- // (SDK rate_limit_event), {resetsAt} (agent-connection resume notice), and
2026
- // the usage-sampler ({rateLimitType, utilization, status, resetsAt, gauges}
2027
- // — resetsAt matches rateLimitType; gauges survives via .passthrough()).
2157
+ // Four producer shapes share this type: {rateLimitType, utilization, status}
2158
+ // (SDK rate_limit_event), {resetsAt} (agent-connection resume notice), the
2159
+ // usage-sampler ({rateLimitType, utilization, status, resetsAt, gauges}
2160
+ // — resetsAt matches rateLimitType; gauges survives via .passthrough()), and
2161
+ // {unmeasurable, reason} (the sampler reporting it cannot read this key).
2028
2162
  z3.object({
2029
2163
  type: z3.literal("rate_limit_update"),
2030
2164
  rateLimitType: z3.string().optional(),
2031
2165
  utilization: z3.number().optional(),
2032
2166
  status: z3.string().optional(),
2033
- resetsAt: z3.string().optional()
2167
+ resetsAt: z3.string().optional(),
2168
+ unmeasurable: z3.boolean().optional(),
2169
+ reason: z3.string().optional()
2034
2170
  }).passthrough(),
2035
2171
  z3.object({
2036
2172
  type: z3.literal("context_compacted"),
@@ -2074,6 +2210,7 @@ var KnownAgentEventSchema = z3.discriminatedUnion("type", [
2074
2210
  z3.object({
2075
2211
  type: z3.literal("setup_complete"),
2076
2212
  startCommandRunning: z3.boolean().optional(),
2213
+ startCommandConfigured: z3.boolean().optional(),
2077
2214
  // Sanitized server-side by sanitizeSessionPreviewPorts — stays unknown.
2078
2215
  previewPorts: z3.unknown().optional()
2079
2216
  }).passthrough(),
@@ -2298,6 +2435,13 @@ var DeleteSubtaskRequestSchema = z4.object({
2298
2435
  sessionId: z4.string(),
2299
2436
  subtaskId: z4.string()
2300
2437
  });
2438
+ var SetSubtaskParentRequestSchema = z4.object({
2439
+ sessionId: z4.string(),
2440
+ taskId: z4.string().min(1),
2441
+ detach: z4.boolean().optional(),
2442
+ ordinal: z4.number().int().nonnegative().optional(),
2443
+ followParentStatus: z4.boolean().optional()
2444
+ });
2301
2445
  var GetTaskPropertiesRequestSchema = z4.object({
2302
2446
  sessionId: z4.string()
2303
2447
  });
@@ -2481,7 +2625,14 @@ var EmitAgentEventRequestSchema = z4.object({
2481
2625
  events: z4.array(AgentEventSchema).max(500)
2482
2626
  });
2483
2627
  var RefreshGithubTokenRequestSchema = z4.object({
2484
- sessionId: z4.string()
2628
+ sessionId: z4.string(),
2629
+ forceFresh: z4.boolean().optional()
2630
+ });
2631
+ var ReportCredentialFailureRequestSchema = z4.object({
2632
+ sessionId: z4.string(),
2633
+ error: z4.string().max(2e3).optional(),
2634
+ tokenShape: z4.string().max(500).optional(),
2635
+ healed: z4.boolean().optional()
2485
2636
  });
2486
2637
  var ReportReviewSpawnFailureRequestSchema = z4.object({
2487
2638
  sessionId: z4.string(),
@@ -2621,6 +2772,18 @@ var SessionStopResponseSchema = z4.object({
2621
2772
  var DeleteSubtaskResponseSchema = z4.object({
2622
2773
  deleted: z4.boolean()
2623
2774
  });
2775
+ var GIT_BRANCH_NAME_MAX = 255;
2776
+ var GIT_BRANCH_NAME_MESSAGE = "Invalid git branch name \u2014 use only letters, numbers, '.', '_', '/' and '-', starting with a letter or number, with no '..', '@{' or '//', and no trailing '/', '-', '.' or '.lock'";
2777
+ var ALLOWED_REF = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;
2778
+ function isValidGitBranchName(name) {
2779
+ if (typeof name !== "string") return false;
2780
+ if (name.length === 0 || name.length > GIT_BRANCH_NAME_MAX) return false;
2781
+ if (!ALLOWED_REF.test(name)) return false;
2782
+ if (name.includes("..") || name.includes("@{") || name.includes("//")) return false;
2783
+ if (name.endsWith("/") || name.endsWith("-")) return false;
2784
+ if (name.endsWith(".") || name.endsWith(".lock")) return false;
2785
+ return true;
2786
+ }
2624
2787
  var cardDescription2 = z5.string().max(CARD_DESCRIPTION_MAX, CARD_DESCRIPTION_LIMIT_MESSAGE).optional();
2625
2788
  var ListAccessibleProjectsRequestSchema = z5.object({
2626
2789
  pageSize: z5.number().int().positive().max(100).optional().default(100)
@@ -2737,6 +2900,16 @@ var CreateProjectTaskRequestSchema = z5.object({
2737
2900
  subProjectId: z5.string().nullable().optional(),
2738
2901
  requestingUserId: z5.string().optional()
2739
2902
  });
2903
+ var SetProjectTaskParentRequestSchema = z5.object({
2904
+ projectId: z5.string(),
2905
+ /** Card to move — id or slug. */
2906
+ taskId: z5.string().min(1),
2907
+ /** New parent (id or slug), or null to detach. */
2908
+ parentTaskId: z5.string().min(1).nullable(),
2909
+ ordinal: z5.number().int().nonnegative().optional(),
2910
+ followParentStatus: z5.boolean().optional(),
2911
+ requestingUserId: z5.string().optional()
2912
+ }).strict();
2740
2913
  var UpdateProjectTaskRequestSchema = z5.object({
2741
2914
  projectId: z5.string(),
2742
2915
  taskId: z5.string(),
@@ -2758,11 +2931,17 @@ var UpdateProjectTaskRequestSchema = z5.object({
2758
2931
  // Move to a different sub-project board, or null to move to the parent board.
2759
2932
  // Validated to belong to `projectId` in the handler.
2760
2933
  subProjectId: z5.string().nullable().optional(),
2934
+ // Record the task's ACTUAL working branch (e.g. a locally-driven pack's
2935
+ // branch, so identification never mints a competing name and pack-child
2936
+ // merge handling matches reality), or null to detach. Guarded in the
2937
+ // handler: the ref must exist on origin and no live workspace may be bound
2938
+ // to a different branch.
2939
+ githubBranch: z5.string().min(1).max(GIT_BRANCH_NAME_MAX).refine(isValidGitBranchName, { message: GIT_BRANCH_NAME_MESSAGE }).nullable().optional(),
2761
2940
  requestingUserId: z5.string().optional()
2762
2941
  }).strict().refine(
2763
- (v) => v.title !== void 0 || v.description !== void 0 || v.plan !== void 0 || v.status !== void 0 || v.risk !== void 0 || v.storyPointValue !== void 0 || v.assignedUserId !== void 0 || v.subProjectId !== void 0,
2942
+ (v) => v.title !== void 0 || v.description !== void 0 || v.plan !== void 0 || v.status !== void 0 || v.risk !== void 0 || v.storyPointValue !== void 0 || v.assignedUserId !== void 0 || v.subProjectId !== void 0 || v.githubBranch !== void 0,
2764
2943
  {
2765
- message: "update_task requires at least one field to change (title, description, plan, status, risk, storyPointValue, assignedUserId, or subProjectId)"
2944
+ message: "update_task requires at least one field to change (title, description, plan, status, risk, storyPointValue, assignedUserId, subProjectId, or githubBranch)"
2766
2945
  }
2767
2946
  );
2768
2947
  var TransitionProjectTaskStatusRequestSchema = z5.object({
@@ -3306,7 +3485,7 @@ var AGENT_CHAT_HISTORY_FETCH_LIMIT = Math.max(TASK_CHAT_HISTORY_LIMIT, PM_CHAT_H
3306
3485
  function formatModelId(provider, model) {
3307
3486
  return `${provider}/${model}`;
3308
3487
  }
3309
- function anthropicEntry(model, label, inputPerMillion, outputPerMillion, experimental = false) {
3488
+ function anthropicEntry(model, label, inputPerMillion, outputPerMillion, opts = {}) {
3310
3489
  return {
3311
3490
  provider: "anthropic",
3312
3491
  model,
@@ -3316,7 +3495,8 @@ function anthropicEntry(model, label, inputPerMillion, outputPerMillion, experim
3316
3495
  inputPrice: inputPerMillion / 1e6,
3317
3496
  outputPrice: outputPerMillion / 1e6,
3318
3497
  supportsTools: true,
3319
- ...experimental ? { experimental: true } : {}
3498
+ supportsEffort: opts.supportsEffort ?? true,
3499
+ ...opts.experimental ? { experimental: true } : {}
3320
3500
  };
3321
3501
  }
3322
3502
  var ANTHROPIC_CATALOG = [
@@ -3324,8 +3504,9 @@ var ANTHROPIC_CATALOG = [
3324
3504
  anthropicEntry(PREVIOUS_OPUS_MODEL, "Opus 4.8", 5, 25),
3325
3505
  anthropicEntry(DEFAULT_SONNET_MODEL, "Sonnet 5 Latest", 3, 15),
3326
3506
  anthropicEntry(PREVIOUS_SONNET_MODEL, "Sonnet 4.6", 3, 15),
3327
- anthropicEntry(DEFAULT_HAIKU_MODEL, "Haiku 4.5", 1, 5),
3328
- anthropicEntry(FABLE_MODEL, "Fable 5 (experimental)", 10, 50, true)
3507
+ // The Haiku line (4.5 and older) predates the tuning surface and 400s on it.
3508
+ anthropicEntry(DEFAULT_HAIKU_MODEL, "Haiku 4.5", 1, 5, { supportsEffort: false }),
3509
+ anthropicEntry(FABLE_MODEL, "Fable 5 (experimental)", 10, 50, { experimental: true })
3329
3510
  ];
3330
3511
  var HUMAN_PROSE_WRITING_STYLE = `## Writing style for humans
3331
3512
  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):
@@ -7378,7 +7559,7 @@ function findOnPath(binary, env = process.env) {
7378
7559
  import { promises as fs } from "fs";
7379
7560
  import { dirname as dirname2, join as join9 } from "path";
7380
7561
  import { homedir as homedir4 } from "os";
7381
- var logger = createServiceLogger("opencode-auth");
7562
+ var logger2 = createServiceLogger("opencode-auth");
7382
7563
  var OPENCODE_CODEX_PLUGIN = "opencode-openai-codex-auth@4.4.0";
7383
7564
  var PLUGIN_PACKAGE = "opencode-openai-codex-auth";
7384
7565
  function opencodeAuthPath(env) {
@@ -7425,7 +7606,7 @@ async function ensureAuthEntry(env, seed) {
7425
7606
  const path2 = opencodeAuthPath(env);
7426
7607
  const store = await readJsonFile(path2);
7427
7608
  if (!shouldSeed(store.openai, seed)) {
7428
- logger.info("opencode oauth store is fresher than the seed; leaving it alone");
7609
+ logger2.info("opencode oauth store is fresher than the seed; leaving it alone");
7429
7610
  return;
7430
7611
  }
7431
7612
  store.openai = {
@@ -7435,7 +7616,7 @@ async function ensureAuthEntry(env, seed) {
7435
7616
  expires: seed.expires
7436
7617
  };
7437
7618
  await writeJsonFile(path2, store);
7438
- logger.info("seeded opencode oauth store entry");
7619
+ logger2.info("seeded opencode oauth store entry");
7439
7620
  }
7440
7621
  async function ensurePluginConfig(env) {
7441
7622
  const path2 = opencodeConfigPath(env);
@@ -7448,7 +7629,7 @@ async function ensurePluginConfig(env) {
7448
7629
  const kept = plugins.filter((p) => !isOurs(p));
7449
7630
  config.plugin = [...kept, OPENCODE_CODEX_PLUGIN];
7450
7631
  await writeJsonFile(path2, config);
7451
- logger.info("ensured opencode codex-auth plugin in config");
7632
+ logger2.info("ensured opencode codex-auth plugin in config");
7452
7633
  }
7453
7634
  async function seedOpenCodeOauth(env) {
7454
7635
  const seed = parseOauthSeed(env.CONVEYOR_OPENCODE_OAUTH);
@@ -7457,7 +7638,7 @@ async function seedOpenCodeOauth(env) {
7457
7638
  await ensureAuthEntry(env, seed);
7458
7639
  await ensurePluginConfig(env);
7459
7640
  } catch (err) {
7460
- logger.warn(
7641
+ logger2.warn(
7461
7642
  `failed to seed opencode oauth store: ${err instanceof Error ? err.message : String(err)}`
7462
7643
  );
7463
7644
  }
@@ -7826,14 +8007,14 @@ function resolveTuiAdapter(kind = "claude-code") {
7826
8007
 
7827
8008
  // src/harness/pty/stream-server.ts
7828
8009
  import net from "net";
7829
- var logger2 = createServiceLogger("PtyStreamServer");
8010
+ var logger3 = createServiceLogger("PtyStreamServer");
7830
8011
  var RING_MAX_CHARS = 256 * 1024;
7831
8012
  var PtyStreamServer = class {
7832
8013
  constructor(options) {
7833
8014
  this.options = options;
7834
8015
  this.server = net.createServer((socket) => this.handleConnection(socket));
7835
8016
  this.server.on("error", (err) => {
7836
- logger2.warn(`PTY stream server error: ${err.message}`);
8017
+ logger3.warn(`PTY stream server error: ${err.message}`);
7837
8018
  });
7838
8019
  }
7839
8020
  options;
@@ -7874,7 +8055,7 @@ var PtyStreamServer = class {
7874
8055
  return port;
7875
8056
  }
7876
8057
  }
7877
- logger2.warn(`PTY stream server could not bind any port in ${base}..${base + attempts - 1}`);
8058
+ logger3.warn(`PTY stream server could not bind any port in ${base}..${base + attempts - 1}`);
7878
8059
  return null;
7879
8060
  }
7880
8061
  tryListen(port) {
@@ -7995,7 +8176,7 @@ var PtyStreamServer = class {
7995
8176
  };
7996
8177
 
7997
8178
  // src/harness/pty/direct-stream.ts
7998
- var logger3 = createServiceLogger("PtyDirectStream");
8179
+ var logger4 = createServiceLogger("PtyDirectStream");
7999
8180
  var RELAY_COALESCE_MS = 2e3;
8000
8181
  var RELAY_MAX_BUFFER_CHARS = 48 * 1024;
8001
8182
  var DirectStreamController = class {
@@ -8044,7 +8225,7 @@ var DirectStreamController = class {
8044
8225
  });
8045
8226
  void created.listen().then((port) => this.onListening(created, port)).catch((err) => {
8046
8227
  this.starting = false;
8047
- logger3.warn(
8228
+ logger4.warn(
8048
8229
  `PTY stream server failed to start: ${err instanceof Error ? err.message : String(err)}`
8049
8230
  );
8050
8231
  });
@@ -8057,7 +8238,7 @@ var DirectStreamController = class {
8057
8238
  }
8058
8239
  this.server = created;
8059
8240
  this.reporter.reportPtyStream(port);
8060
- logger3.info(`PTY stream server listening on ${port} (session ${this.reporter.sessionId})`);
8241
+ logger4.info(`PTY stream server listening on ${port} (session ${this.reporter.sessionId})`);
8061
8242
  }
8062
8243
  /** Push the min box across both transports to the pty. */
8063
8244
  applyDims() {
@@ -10083,7 +10264,10 @@ var getTaskContract = defineToolContract({
10083
10264
  fields: {
10084
10265
  projectId: mcpProjectId,
10085
10266
  taskId: f.string({
10086
- desc: "The task ID or slug (the value in a card URL, /cards/<slug>)"
10267
+ // The path form is MCP-only on purpose: an in-pod agent is bound to one
10268
+ // project, where a bare slug already resolves, so the `agent` half of
10269
+ // this contract deliberately says nothing about it.
10270
+ desc: "The task ID or slug (the value in a card URL, /cards/<slug>), or a path-form reference '<project-slug>/<card-slug>' from the board's Copy path action"
10087
10271
  })
10088
10272
  }
10089
10273
  }
@@ -10625,11 +10809,46 @@ var listSubtasksContract = defineToolContract({
10625
10809
  }
10626
10810
  }
10627
10811
  });
10812
+ var SET_PARENT_ORDINAL = "Position among the new parent's children. Defaults to after every existing sibling. A detached card has no siblings to order against, so it only matters when you adopt.";
10813
+ var SET_PARENT_WARNINGS = "The move is reported, never refused: a card that already has a pull request, live compute, a release, children of its own, or dependency edges that now cross packs comes back with a warning in the result. Its branch is NOT re-cut against the new parent's feature branch.";
10814
+ var setTaskParentContract = defineToolContract({
10815
+ name: "set_task_parent",
10816
+ agent: {
10817
+ description: `Adopt an EXISTING card into this card's pack, or eject one of this card's children. The card keeps its own chat, plan, and history \u2014 it moves under (or out from) the current card, inherits the board, and lands after the current children. To make a NEW child use create_subtask instead. ${SET_PARENT_WARNINGS}`,
10818
+ fields: {
10819
+ taskId: f.string({
10820
+ desc: "Id, slug, or branch name of the card to move. Must live in this project."
10821
+ }),
10822
+ detach: f.optional(
10823
+ f.boolean({
10824
+ desc: "Eject the card from this pack instead of adopting it \u2014 it becomes a standalone card and stops following the parent's status. The card must already be a child of the current card."
10825
+ })
10826
+ ),
10827
+ ordinal: f.optional(f.number({ desc: SET_PARENT_ORDINAL })),
10828
+ followParentStatus: f.optional(f.boolean({ desc: AGENT_FOLLOW_PARENT_STATUS }))
10829
+ }
10830
+ },
10831
+ mcp: {
10832
+ description: `Re-parent an existing card: pass a parentTaskId to adopt it into that card's pack, or null to detach it into a standalone card. The card keeps its chat, plan, and history; adopting also moves it to the parent's board and orders it after the parent's existing children. Use create_subtask for a NEW child, and move_card to move a card to another PROJECT. Pass projectId to target a specific project; otherwise the configured default project is used. ${SET_PARENT_WARNINGS}`,
10833
+ fields: {
10834
+ projectId: mcpProjectId,
10835
+ taskId: f.string({ desc: "Id or slug of the card to move" }),
10836
+ parentTaskId: f.nullable(
10837
+ f.string({
10838
+ desc: "Id or slug of the new parent, or null to detach the card from its current parent. Must be in the same project, and can be neither the card itself nor one of its own descendants."
10839
+ })
10840
+ ),
10841
+ ordinal: f.optional(f.number({ desc: SET_PARENT_ORDINAL })),
10842
+ followParentStatus: f.optional(f.boolean({ desc: MCP_FOLLOW_PARENT_STATUS }))
10843
+ }
10844
+ }
10845
+ });
10628
10846
  var subtasksContracts = [
10629
10847
  createSubtaskContract,
10630
10848
  updateSubtaskContract,
10631
10849
  deleteSubtaskContract,
10632
- listSubtasksContract
10850
+ listSubtasksContract,
10851
+ setTaskParentContract
10633
10852
  ];
10634
10853
  var mcpTaskIdOrSlug = f.string({ desc: "The task ID or slug" });
10635
10854
  var listTaskFilesContract = defineToolContract({
@@ -11064,6 +11283,80 @@ function buildGetSuggestionsTool(connection) {
11064
11283
 
11065
11284
  // src/tools/mutation-tools.ts
11066
11285
  import { z as z13 } from "zod";
11286
+
11287
+ // src/runner/refresh-verify-heal.ts
11288
+ async function refreshAndVerifyGithubCredential(cwd, mint) {
11289
+ const first = await mintWriteProbe(cwd, mint, false);
11290
+ if (first.probe.ok) return first;
11291
+ if (first.probe.outcome !== "denied") return first;
11292
+ if (!first.written) return first;
11293
+ const healed = await mintWriteProbe(cwd, mint, true);
11294
+ return { ...healed, healAttempted: true };
11295
+ }
11296
+ async function mintWriteProbe(cwd, mint, forceFresh) {
11297
+ let token;
11298
+ try {
11299
+ token = await mint(forceFresh ? { forceFresh: true } : {});
11300
+ } catch (err) {
11301
+ return {
11302
+ ok: false,
11303
+ probe: { ok: false, outcome: "unavailable", error: errorText(err) },
11304
+ healAttempted: false,
11305
+ failures: [`- could not mint a GitHub token: ${errorText(err)}`]
11306
+ };
11307
+ }
11308
+ if (!token) {
11309
+ return {
11310
+ ok: false,
11311
+ probe: { ok: false, outcome: "unavailable", error: "no token returned" },
11312
+ healAttempted: false,
11313
+ failures: ["- the server returned no GitHub token"]
11314
+ };
11315
+ }
11316
+ const written = await updateRemoteToken(cwd, token);
11317
+ process.env.GITHUB_TOKEN = token;
11318
+ process.env.GH_TOKEN = token;
11319
+ const probe = await verifyGitCredential(cwd);
11320
+ const failures = describeFailures(written, probe);
11321
+ return {
11322
+ ok: probe.ok && failures.length === 0,
11323
+ probe,
11324
+ written,
11325
+ healAttempted: false,
11326
+ failures,
11327
+ ...probe.ok ? {} : { tokenShape: describeTokenFile() }
11328
+ };
11329
+ }
11330
+ function describeFailures(written, probe) {
11331
+ const failures = [];
11332
+ const credentialError = written.credential.error ? `: ${written.credential.error}` : "";
11333
+ if (!written.credential.storeWritten) {
11334
+ failures.push(`- git credential store NOT written${credentialError}`);
11335
+ } else if (!written.credential.helperConfigured) {
11336
+ failures.push(`- git credential helper NOT configured${credentialError}`);
11337
+ }
11338
+ if (!written.files.tokenFile) {
11339
+ const why = written.files.errors?.tokenFile;
11340
+ failures.push(`- token file NOT written${why ? `: ${why}` : ""}`);
11341
+ }
11342
+ if (!written.files.ghHosts && !ghHostsExternallyOwned()) {
11343
+ const why = written.files.errors?.ghHosts;
11344
+ failures.push(`- gh CLI config NOT written${why ? `: ${why}` : ""}`);
11345
+ }
11346
+ if (probe.outcome === "denied") {
11347
+ failures.push(`- git could not authenticate to origin: ${probe.error ?? "unknown error"}`);
11348
+ } else if (probe.outcome === "timeout") {
11349
+ failures.push(
11350
+ `- the credential is UNPROVEN: the check against origin timed out (${probe.error ?? "killed"}). The pod is likely under load \u2014 this is not evidence the token is bad.`
11351
+ );
11352
+ }
11353
+ return failures;
11354
+ }
11355
+ function errorText(err) {
11356
+ return err instanceof Error ? err.message : String(err);
11357
+ }
11358
+
11359
+ // src/tools/mutation-tools.ts
11067
11360
  function buildPostToChatTool(connection) {
11068
11361
  return defineContractTool(
11069
11362
  postToChatContract,
@@ -11353,60 +11646,85 @@ function buildVoteSuggestionTool(connection) {
11353
11646
  }
11354
11647
  );
11355
11648
  }
11649
+ function describeRefreshOutcome(outcome, tokenFile) {
11650
+ const healedNote = outcome.healAttempted ? " (the first attempt was rejected; a force-fresh retry fixed it)" : "";
11651
+ if (outcome.probe.ok) {
11652
+ if (outcome.failures.length === 0) {
11653
+ return {
11654
+ report: false,
11655
+ text: [
11656
+ `GitHub token refreshed and verified against origin${healedNote}.`,
11657
+ "- git: the credential store and helper are updated, so `git push` works now with no extra step.",
11658
+ "- gh: the CLI config is updated, so `gh` works now with no extra step.",
11659
+ `- shell/scripts: read the current token from ${tokenFile}.`,
11660
+ ` For a command that needs it in the environment: GITHUB_TOKEN=$(cat ${tokenFile}) <command>`,
11661
+ "Retry the failed command."
11662
+ ].join("\n")
11663
+ };
11664
+ }
11665
+ return {
11666
+ report: false,
11667
+ text: [
11668
+ `GitHub token refreshed, but some credential copies did not update.`,
11669
+ ...outcome.failures,
11670
+ `A current token is available at ${tokenFile} when that file was written.`
11671
+ ].join("\n")
11672
+ };
11673
+ }
11674
+ if (outcome.probe.outcome === "timeout") {
11675
+ return {
11676
+ report: false,
11677
+ text: [
11678
+ "GitHub token refreshed, but the check against origin timed out \u2014 the credential is UNPROVEN, not known bad.",
11679
+ ...outcome.failures,
11680
+ "The pod is probably under load (a heavy gate). Retry your git command directly; if it succeeds, nothing is wrong."
11681
+ ].join("\n")
11682
+ };
11683
+ }
11684
+ if (outcome.probe.outcome === "unavailable") {
11685
+ return {
11686
+ report: false,
11687
+ text: [
11688
+ "The server did not hand back a GitHub token, so nothing was refreshed and nothing was tested.",
11689
+ ...outcome.failures,
11690
+ "The agent's connection to the API may be down, or this project may have no GitHub App installed. Wait for the connection to recover and try again."
11691
+ ].join("\n")
11692
+ };
11693
+ }
11694
+ return {
11695
+ report: true,
11696
+ text: [
11697
+ `GitHub token refresh did NOT produce a working git credential${outcome.healAttempted ? ", including after a force-fresh retry" : ""}.`,
11698
+ ...outcome.failures,
11699
+ ...outcome.tokenShape ? [`- credential in play: ${outcome.tokenShape}`] : [],
11700
+ "This has been reported to the card, so the team can see it.",
11701
+ "Do not retry this tool. **Your unpushed work is only on this pod** \u2014 the WIP snapshot needs the same credential, so it is not being saved either. Preserve it before anything else: `git bundle create /tmp/work.bundle --all` (or `git format-patch`), then attach that file to the card with `upload_attachment`."
11702
+ ].join("\n")
11703
+ };
11704
+ }
11356
11705
  function buildRefreshGithubTokenTool(connection, config) {
11357
11706
  return defineTool(
11358
11707
  "refresh_github_token",
11359
- "Mint a fresh GitHub token for this pod when git or gh fails with a 401 / 'Bad credentials'. The pod's token expires about every hour. This updates the git credential store and the gh CLI config, and returns the shell command that puts a current token in your environment.",
11708
+ "Mint a fresh GitHub token for this pod when git or gh fails with a 401 / 'Bad credentials'. The pod's token expires about every hour. This updates the git credential store and the gh CLI config, verifies the result against origin, and returns the shell command that puts a current token in your environment.",
11360
11709
  {},
11361
11710
  async () => {
11362
11711
  try {
11363
- const result = await connection.call("refreshGithubToken", {
11364
- sessionId: connection.sessionId
11365
- });
11366
- const written = await updateRemoteToken(config.workspaceDir, result.token);
11367
- process.env.GITHUB_TOKEN = result.token;
11368
- process.env.GH_TOKEN = result.token;
11369
- const tokenFile = githubTokenFilePath();
11370
- const failures = [];
11371
- if (!written.credential.storeWritten) {
11372
- failures.push(
11373
- `- git credential store NOT written${written.credential.error ? `: ${written.credential.error}` : ""}`
11374
- );
11375
- } else if (!written.credential.helperConfigured) {
11376
- failures.push(
11377
- `- git credential helper NOT configured${written.credential.error ? `: ${written.credential.error}` : ""}`
11378
- );
11379
- }
11380
- if (!written.files.tokenFile) failures.push(`- token file ${tokenFile} NOT written`);
11381
- if (!written.files.ghHosts && !ghHostsExternallyOwned()) {
11382
- failures.push("- gh CLI config NOT written");
11383
- }
11384
- const probe = await verifyGitCredential(config.workspaceDir);
11385
- if (!probe.ok) {
11386
- failures.push(
11387
- `- git could not authenticate to origin: ${probe.error ?? "unknown error"}`
11388
- );
11389
- }
11390
- if (failures.length > 0) {
11391
- return textResult(
11392
- [
11393
- probe.ok ? "GitHub token refreshed, but some credential copies did not update." : "GitHub token refresh did NOT produce a working git credential.",
11394
- ...failures,
11395
- `A current token is available at ${tokenFile} when that file was written.`,
11396
- "Do not retry this tool more than once. Report the failing lines above to the team \u2014 the GitHub App install may be missing for this project, or the pod's credential directory may not be writable."
11397
- ].join("\n")
11398
- );
11399
- }
11400
- return textResult(
11401
- [
11402
- "GitHub token refreshed and verified against origin.",
11403
- "- git: the credential store and helper are updated, so `git push` works now with no extra step.",
11404
- "- gh: the CLI config is updated, so `gh` works now with no extra step.",
11405
- `- shell/scripts: read the current token from ${tokenFile}.`,
11406
- ` For a command that needs it in the environment: GITHUB_TOKEN=$(cat ${tokenFile}) <command>`,
11407
- "Retry the failed command."
11408
- ].join("\n")
11712
+ const outcome = await refreshAndVerifyGithubCredential(
11713
+ config.workspaceDir,
11714
+ (opts) => connection.call("refreshGithubToken", {
11715
+ sessionId: connection.sessionId,
11716
+ ...opts?.forceFresh ? { forceFresh: true } : {}
11717
+ }).then((r) => r.token)
11409
11718
  );
11719
+ const { text, report } = describeRefreshOutcome(outcome, githubTokenFilePath());
11720
+ if (report) {
11721
+ connection.reportCredentialFailure({
11722
+ error: outcome.failures.join("\n"),
11723
+ ...outcome.tokenShape ? { tokenShape: outcome.tokenShape } : {},
11724
+ healed: outcome.healAttempted
11725
+ });
11726
+ }
11727
+ return textResult(text);
11410
11728
  } catch (error) {
11411
11729
  return textResult(
11412
11730
  `Failed to refresh the GitHub token: ${error instanceof Error ? error.message : "Unknown error"}
@@ -11773,12 +12091,33 @@ function buildCreateSubtaskTool(connection) {
11773
12091
  });
11774
12092
  const unmatched = result.unmatchedTags ?? [];
11775
12093
  const tagNote = unmatched.length > 0 ? ` These tag names matched no project tag and were skipped: ${unmatched.join(", ")}. Use list_tags to see the glossary.` : "";
12094
+ return textResult(`Subtask created with ID: ${result.id} (slug: ${result.slug})${tagNote}`);
12095
+ } catch (error) {
11776
12096
  return textResult(
11777
- `Subtask created with ID: ${result.id} (slug: ${result.slug})${tagNote}`
12097
+ `Failed to create subtask: ${error instanceof Error ? error.message : "Unknown error"}`
11778
12098
  );
12099
+ }
12100
+ }
12101
+ );
12102
+ }
12103
+ function buildSetTaskParentTool(connection) {
12104
+ return defineContractTool(
12105
+ setTaskParentContract,
12106
+ async ({ taskId, detach, ordinal, followParentStatus }) => {
12107
+ try {
12108
+ const result = await connection.call("setSubtaskParent", {
12109
+ sessionId: connection.sessionId,
12110
+ taskId,
12111
+ ...detach !== void 0 && { detach },
12112
+ ...ordinal !== void 0 && { ordinal },
12113
+ ...followParentStatus !== void 0 && { followParentStatus }
12114
+ });
12115
+ const warnings = result.warnings.length > 0 ? ` Warnings: ${result.warnings.join(", ")}.` : "";
12116
+ const move = detach ? `Card ${result.slug} detached \u2014 it is now a standalone card` : `Card ${result.slug} adopted into this pack`;
12117
+ return textResult(`${move} (status: ${result.status}).${warnings}`);
11779
12118
  } catch (error) {
11780
12119
  return textResult(
11781
- `Failed to create subtask: ${error instanceof Error ? error.message : "Unknown error"}`
12120
+ `Failed to set task parent: ${error instanceof Error ? error.message : "Unknown error"}`
11782
12121
  );
11783
12122
  }
11784
12123
  }
@@ -11925,6 +12264,7 @@ function buildPmTools(connection, options) {
11925
12264
  const tools = [
11926
12265
  buildUpdateTaskTool(connection),
11927
12266
  buildCreateSubtaskTool(connection),
12267
+ buildSetTaskParentTool(connection),
11928
12268
  buildUpdateSubtaskTool(connection),
11929
12269
  buildDeleteSubtaskTool(connection),
11930
12270
  buildListSubtasksTool(connection)
@@ -12893,7 +13233,7 @@ function resolvePlaywrightMcpServer(env = process.env) {
12893
13233
  }
12894
13234
 
12895
13235
  // src/execution/event-handlers.ts
12896
- var logger4 = createServiceLogger("event-handlers");
13236
+ var logger5 = createServiceLogger("event-handlers");
12897
13237
  function safeVoid(promise, context) {
12898
13238
  if (promise && typeof promise.catch === "function") {
12899
13239
  promise.catch((err) => {
@@ -13048,7 +13388,7 @@ async function emitResultEvent(event, host, context, startTime, lastAssistantUsa
13048
13388
  }
13049
13389
  function handleRateLimitEvent(event, host) {
13050
13390
  const { rate_limit_info } = event;
13051
- logger4.info("Rate limit event received", { rate_limit_info });
13391
+ logger5.info("Rate limit event received", { rate_limit_info });
13052
13392
  const status = rate_limit_info.status;
13053
13393
  const utilization = rate_limit_info.utilization ?? (status === "rejected" ? 1 : void 0);
13054
13394
  if (utilization !== void 0 && rate_limit_info.rateLimitType) {
@@ -13772,7 +14112,7 @@ function buildCanUseTool(host) {
13772
14112
  }
13773
14113
 
13774
14114
  // src/execution/query-executor.ts
13775
- var logger5 = createServiceLogger("QueryExecutor");
14115
+ var logger6 = createServiceLogger("QueryExecutor");
13776
14116
  var IMAGE_ERROR_PATTERN2 = /Could not process image/i;
13777
14117
  var RETRY_DELAYS_MS2 = [6e4, 12e4, 18e4, 3e5];
13778
14118
  function buildHooks(host) {
@@ -13864,7 +14204,7 @@ function repairTornSessionFile(path2) {
13864
14204
  }
13865
14205
  if (keepEnd === content.length) return false;
13866
14206
  truncateSync(path2, Buffer.byteLength(content.slice(0, keepEnd), "utf8"));
13867
- logger5.warn("Repaired torn transcript before resume", {
14207
+ logger6.warn("Repaired torn transcript before resume", {
13868
14208
  path: path2,
13869
14209
  trimmedBytes: content.length - keepEnd
13870
14210
  });
@@ -13945,7 +14285,7 @@ function buildQueryOptions(host, context) {
13945
14285
  disallowedTools: buildDisallowedTools(settings, mode, host.hasExitedPlanMode),
13946
14286
  enableFileCheckpointing: settings.enableFileCheckpointing,
13947
14287
  stderr: (data) => {
13948
- logger5.warn("Claude Code stderr", { data: data.trimEnd() });
14288
+ logger6.warn("Claude Code stderr", { data: data.trimEnd() });
13949
14289
  }
13950
14290
  };
13951
14291
  }
@@ -14501,7 +14841,7 @@ async function runWithRetry(initialQuery, context, host, options) {
14501
14841
  }
14502
14842
 
14503
14843
  // src/runner/query-bridge.ts
14504
- var logger6 = createServiceLogger("QueryBridge");
14844
+ var logger7 = createServiceLogger("QueryBridge");
14505
14845
  function resolveHarnessKind() {
14506
14846
  if (process.env.CONVEYOR_FORCE_SDK_CARDS === "1") return "sdk";
14507
14847
  return "pty";
@@ -14683,9 +15023,9 @@ var QueryBridge = class {
14683
15023
  const msg = err instanceof Error ? err.message : String(err);
14684
15024
  const isAbort = this._stopped || /abort/i.test(msg);
14685
15025
  if (isAbort) {
14686
- logger6.info("Query stopped by user", { error: msg });
15026
+ logger7.info("Query stopped by user", { error: msg });
14687
15027
  } else {
14688
- logger6.error("Query execution failed", { error: msg });
15028
+ logger7.error("Query execution failed", { error: msg });
14689
15029
  this.connection.sendEvent({ type: "error", message: msg });
14690
15030
  }
14691
15031
  } finally {
@@ -14711,9 +15051,9 @@ var QueryBridge = class {
14711
15051
  const msg = err instanceof Error ? err.message : String(err);
14712
15052
  const isAbort = this._stopped || /abort/i.test(msg);
14713
15053
  if (isAbort) {
14714
- logger6.info("Passive turn stopped", { error: msg });
15054
+ logger7.info("Passive turn stopped", { error: msg });
14715
15055
  } else {
14716
- logger6.error("Passive turn failed", { error: msg });
15056
+ logger7.error("Passive turn failed", { error: msg });
14717
15057
  this.connection.sendEvent({ type: "error", message: msg });
14718
15058
  }
14719
15059
  } finally {
@@ -15040,7 +15380,8 @@ async function runUsageProbe(deps = {}) {
15040
15380
  }
15041
15381
 
15042
15382
  // src/execution/usage-sampler.ts
15043
- var logger7 = createServiceLogger("usage-sampler");
15383
+ var logger8 = createServiceLogger("usage-sampler");
15384
+ var NO_SAMPLES = { samples: [], unmeasurable: null };
15044
15385
  function isAttributable(identity, sessionToken) {
15045
15386
  if (!identity) return { ok: true };
15046
15387
  if (identity.hasRefreshToken) {
@@ -15052,14 +15393,14 @@ function isAttributable(identity, sessionToken) {
15052
15393
  return { ok: true };
15053
15394
  }
15054
15395
  async function sampleKeyUsage(token, probe = () => runUsageProbe(), hasSubscriptionCredentials = () => existsSync3(claudeCredentialsPath()), readIdentity = readCredentialsIdentity) {
15055
- if (!token && !hasSubscriptionCredentials()) return [];
15396
+ if (!token && !hasSubscriptionCredentials()) return NO_SAMPLES;
15056
15397
  try {
15057
15398
  const attributable = isAttributable(await readIdentity(), token);
15058
15399
  if (!attributable.ok) {
15059
- logger7.info("usage sample skipped \u2014 credentials not attributable to this session's key", {
15400
+ logger8.info("usage sample skipped \u2014 credentials not attributable to this session's key", {
15060
15401
  reason: attributable.reason
15061
15402
  });
15062
- return [];
15403
+ return { samples: [], unmeasurable: { reason: attributable.reason ?? "unattributable" } };
15063
15404
  }
15064
15405
  const stdout = await probe();
15065
15406
  const { sessionUsage, weeklyUsage, sessionResetsAt, weeklyResetsAt, gauges } = parseUsageGauges(stdout);
@@ -15083,18 +15424,37 @@ async function sampleKeyUsage(token, probe = () => runUsageProbe(), hasSubscript
15083
15424
  });
15084
15425
  }
15085
15426
  if (samples.length === 0) {
15086
- logger7.info("usage sample produced no gauges", {
15427
+ logger8.info("usage sample produced no gauges", {
15087
15428
  stdoutLength: stdout.length,
15088
15429
  stdoutHead: stdout.slice(0, 200).replaceAll("\n", " ")
15089
15430
  });
15090
15431
  }
15091
- return samples;
15432
+ return { samples, unmeasurable: null };
15092
15433
  } catch (error) {
15093
- logger7.info("usage sample failed", {
15434
+ logger8.info("usage sample failed", {
15094
15435
  error: error instanceof Error ? error.message : String(error)
15095
15436
  });
15096
- return [];
15097
- }
15437
+ return NO_SAMPLES;
15438
+ }
15439
+ }
15440
+ function buildRateLimitEvents(samples, codingAgentKeyId) {
15441
+ return samples.map((sample) => ({
15442
+ type: "rate_limit_update",
15443
+ rateLimitType: sample.rateLimitType,
15444
+ utilization: sample.utilization,
15445
+ status: sample.status,
15446
+ resetsAt: sample.resetsAt ?? void 0,
15447
+ gauges: sample.gauges,
15448
+ ...codingAgentKeyId ? { codingAgentKeyId } : {}
15449
+ }));
15450
+ }
15451
+ function buildUnmeasurableEvent(reason, codingAgentKeyId) {
15452
+ return {
15453
+ type: "rate_limit_update",
15454
+ unmeasurable: true,
15455
+ reason,
15456
+ ...codingAgentKeyId ? { codingAgentKeyId } : {}
15457
+ };
15098
15458
  }
15099
15459
 
15100
15460
  // src/setup/git-ready.ts
@@ -16243,12 +16603,9 @@ var SessionRunner = class _SessionRunner {
16243
16603
  /** Shared token-refresh closure for the git-flush call sites (periodic
16244
16604
  * backstop + per-turn flush). Never throws — returns undefined so the
16245
16605
  * caller falls back to its existing remote credentials. */
16246
- async refreshGithubTokenForFlush() {
16606
+ async refreshGithubTokenForFlush(opts) {
16247
16607
  try {
16248
- const res = await this.connection.call("refreshGithubToken", {
16249
- sessionId: this.connection.sessionId
16250
- });
16251
- return res.token;
16608
+ return await this.mintGithubToken(opts);
16252
16609
  } catch {
16253
16610
  return void 0;
16254
16611
  }
@@ -16263,7 +16620,7 @@ var SessionRunner = class _SessionRunner {
16263
16620
  try {
16264
16621
  await flushPendingChanges(this.config.workspaceDir, {
16265
16622
  wipMessage,
16266
- refreshToken: () => this.refreshGithubTokenForFlush()
16623
+ refreshToken: (opts) => this.refreshGithubTokenForFlush(opts)
16267
16624
  });
16268
16625
  } catch {
16269
16626
  } finally {
@@ -16288,7 +16645,7 @@ var SessionRunner = class _SessionRunner {
16288
16645
  try {
16289
16646
  const result = await flushAllPendingWork(this.config.workspaceDir, {
16290
16647
  wipMessage: "WIP: periodic auto-commit",
16291
- refreshToken: () => this.refreshGithubTokenForFlush()
16648
+ refreshToken: (opts) => this.refreshGithubTokenForFlush(opts)
16292
16649
  });
16293
16650
  if (result.hadWork) {
16294
16651
  process.stderr.write(
@@ -16303,22 +16660,20 @@ var SessionRunner = class _SessionRunner {
16303
16660
  }
16304
16661
  /** Sample the running Claude subscription key's rate-limit utilization from
16305
16662
  * the Claude CLI `/usage` command and report it as `rate_limit_update` events.
16306
- * The API (`persistRateLimitSnapshot`) attributes them to the key this session
16307
- * launched under, keeping User Settings + the PtY-tab usage widget fresh and
16663
+ * The API (`persistRateLimitSnapshot`) attributes them to the key named on the
16664
+ * event, keeping User Settings + the PtY-tab usage widget fresh and
16308
16665
  * `selectBestKey` rotation honest. Best-effort — never throws, no-op when the
16309
16666
  * pod has no OAuth token (e.g. API-key projects). */
16310
16667
  async sampleAndReportKeyUsage() {
16311
16668
  if (this.stopped) return;
16312
- const samples = await sampleKeyUsage(process.env.CLAUDE_CODE_OAUTH_TOKEN);
16313
- for (const sample of samples) {
16314
- this.connection.sendEvent({
16315
- type: "rate_limit_update",
16316
- rateLimitType: sample.rateLimitType,
16317
- utilization: sample.utilization,
16318
- status: sample.status,
16319
- resetsAt: sample.resetsAt ?? void 0,
16320
- gauges: sample.gauges
16321
- });
16669
+ const codingAgentKeyId = process.env.CONVEYOR_CODING_AGENT_KEY_ID;
16670
+ const { samples, unmeasurable } = await sampleKeyUsage(process.env.CLAUDE_CODE_OAUTH_TOKEN);
16671
+ if (unmeasurable) {
16672
+ this.connection.sendEvent(buildUnmeasurableEvent(unmeasurable.reason, codingAgentKeyId));
16673
+ return;
16674
+ }
16675
+ for (const event of buildRateLimitEvents(samples, codingAgentKeyId)) {
16676
+ this.connection.sendEvent(event);
16322
16677
  }
16323
16678
  }
16324
16679
  /** Wait (bounded) for any in-flight periodic/turn flush to release the shared
@@ -16347,16 +16702,7 @@ var SessionRunner = class _SessionRunner {
16347
16702
  try {
16348
16703
  const result = await flushAllPendingWork(this.config.workspaceDir, {
16349
16704
  wipMessage: "WIP: auto-commit on conveyor-agent shutdown",
16350
- refreshToken: async () => {
16351
- try {
16352
- const res = await this.connection.call("refreshGithubToken", {
16353
- sessionId: this.connection.sessionId
16354
- });
16355
- return res.token;
16356
- } catch {
16357
- return void 0;
16358
- }
16359
- }
16705
+ refreshToken: (opts) => this.refreshGithubTokenForFlush(opts)
16360
16706
  });
16361
16707
  if (result.hadWork) {
16362
16708
  process.stderr.write(
@@ -16537,22 +16883,42 @@ var SessionRunner = class _SessionRunner {
16537
16883
  void handlePullBranch(this.config.workspaceDir, branch);
16538
16884
  });
16539
16885
  }
16540
- /** Proactively refresh the GitHub token before the 1-hour expiry. */
16886
+ /**
16887
+ * Proactively refresh the GitHub token before the 1-hour expiry — and PROVE
16888
+ * the result works.
16889
+ *
16890
+ * The old version logged "Proactively refreshed GitHub token" whenever the
16891
+ * credential store write succeeded. It never inspected the token-file write
16892
+ * and never asked git anything, so a pod whose token file was stale printed
16893
+ * that line every 45 minutes while every push 401'd. Three of those lines,
16894
+ * perfectly spaced, are what made the 2026-08-19 occurrence unreadable. A
16895
+ * success line here now means the credential authenticated against origin.
16896
+ */
16541
16897
  async refreshGithubToken() {
16542
16898
  try {
16543
- const res = await this.connection.call("refreshGithubToken", {
16544
- sessionId: this.connection.sessionId
16545
- });
16546
- const written = await updateRemoteToken(this.config.workspaceDir, res.token);
16547
- process.env.GITHUB_TOKEN = res.token;
16548
- process.env.GH_TOKEN = res.token;
16549
- if (written.credential.ok) {
16550
- process.stderr.write("[conveyor-agent] Proactively refreshed GitHub token\n");
16551
- } else {
16899
+ const outcome = await refreshAndVerifyGithubCredential(
16900
+ this.config.workspaceDir,
16901
+ (opts) => this.mintGithubToken(opts)
16902
+ );
16903
+ if (outcome.ok) {
16552
16904
  process.stderr.write(
16553
- `[conveyor-agent] Warning: refreshed GitHub token but the git credential did not update: ${written.credential.error ?? "unknown error"}
16905
+ `[conveyor-agent] Proactively refreshed GitHub token \u2014 verified against origin${outcome.healAttempted ? " (after a force-fresh retry)" : ""}
16554
16906
  `
16555
16907
  );
16908
+ return;
16909
+ }
16910
+ process.stderr.write(
16911
+ `[conveyor-agent] Warning: proactive GitHub token refresh did not produce a working credential:
16912
+ ${outcome.failures.join("\n")}
16913
+ ` + (outcome.tokenShape ? ` token file: ${outcome.tokenShape}
16914
+ ` : "")
16915
+ );
16916
+ if (outcome.probe.outcome === "denied") {
16917
+ this.connection.reportCredentialFailure({
16918
+ error: outcome.failures.join("\n"),
16919
+ ...outcome.tokenShape ? { tokenShape: outcome.tokenShape } : {},
16920
+ healed: outcome.healAttempted
16921
+ });
16556
16922
  }
16557
16923
  } catch (err) {
16558
16924
  const msg = err instanceof Error ? err.message : String(err);
@@ -16560,6 +16926,14 @@ var SessionRunner = class _SessionRunner {
16560
16926
  `);
16561
16927
  }
16562
16928
  }
16929
+ /** Mint a GitHub token over the RPC. `forceFresh` bypasses the API's cache. */
16930
+ async mintGithubToken(opts) {
16931
+ const res = await this.connection.call("refreshGithubToken", {
16932
+ sessionId: this.connection.sessionId,
16933
+ ...opts?.forceFresh ? { forceFresh: true } : {}
16934
+ });
16935
+ return res.token;
16936
+ }
16563
16937
  /** Re-fetch task context to pick up a newly created branch and check it out. */
16564
16938
  async refreshBranchForBuilding() {
16565
16939
  try {
@@ -16732,6 +17106,7 @@ function unshallowRepo(workspaceDir) {
16732
17106
  export {
16733
17107
  fetchBootstrap,
16734
17108
  applyBootstrapToEnv,
17109
+ createServiceLogger,
16735
17110
  AgentConnection,
16736
17111
  DEFAULT_SONNET_MODEL,
16737
17112
  isPermissionDeniedError,
@@ -16739,7 +17114,6 @@ export {
16739
17114
  Lifecycle,
16740
17115
  buildSynthesizedCredentials,
16741
17116
  claudeJsonPath,
16742
- createServiceLogger,
16743
17117
  PtyHarness,
16744
17118
  resolveTuiKindFromEnv,
16745
17119
  resolveTuiAdapter,
@@ -16747,11 +17121,11 @@ export {
16747
17121
  statWorkspacePath,
16748
17122
  workspacePathExists,
16749
17123
  GIT_TIMEOUT_MS,
17124
+ updateRemoteToken,
16750
17125
  hasUncommittedChanges,
16751
17126
  getCurrentBranch,
16752
17127
  hasUnpushedCommits,
16753
17128
  stageAndCommit,
16754
- updateRemoteToken,
16755
17129
  flushPendingChanges,
16756
17130
  pushToOrigin,
16757
17131
  buildProjectTools,
@@ -16760,6 +17134,8 @@ export {
16760
17134
  parseUsageGauges,
16761
17135
  runUsageProbe,
16762
17136
  sampleKeyUsage,
17137
+ buildRateLimitEvents,
17138
+ buildUnmeasurableEvent,
16763
17139
  awaitGitReady,
16764
17140
  PortDiscovery,
16765
17141
  CodespacePortVisibility,
@@ -16769,4 +17145,3 @@ export {
16769
17145
  loadConveyorConfig,
16770
17146
  unshallowRepo
16771
17147
  };
16772
- //# sourceMappingURL=chunk-BAQ2OAKB.js.map