@rallycry/conveyor-agent 10.13.13 → 10.13.14

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.
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-7TQO4ZF4.js";
4
4
  import {
5
5
  getWorkbenchClient
6
- } from "./chunk-YRC57EYG.js";
6
+ } from "./chunk-LZM5OKAC.js";
7
7
  import {
8
8
  workbenchEnabled
9
9
  } from "./chunk-4VUQ2NPF.js";
@@ -11,8 +11,6 @@ import {
11
11
  MAX_BETWEEN_TURN_BUFFER,
12
12
  MAX_DIAGNOSTIC_OUTPUT,
13
13
  buildPromptBytes,
14
- buildSnapshotTar,
15
- extractSnapshotTar,
16
14
  inheritedEnv,
17
15
  loadPtySpawn,
18
16
  parseUserQuestions,
@@ -25,7 +23,7 @@ import {
25
23
  sleep,
26
24
  transcriptSize,
27
25
  turnOptionsFrom
28
- } from "./chunk-2K6RRTQC.js";
26
+ } from "./chunk-RSIW2UCR.js";
29
27
 
30
28
  // src/setup/bootstrap.ts
31
29
  var BOOTSTRAP_TIMEOUT_MS = 3e4;
@@ -207,7 +205,6 @@ var AgentConnection = class _AgentConnection {
207
205
  modeChangeCallback = null;
208
206
  apiKeyUpdateCallback = null;
209
207
  pullBranchCallback = null;
210
- finalizeSnapshotCallback = null;
211
208
  runStartCommandCallback = null;
212
209
  earlyPullBranches = [];
213
210
  spawnReviewCallback = null;
@@ -431,9 +428,6 @@ var AgentConnection = class _AgentConnection {
431
428
  if (this.probeUsageCallback) this.probeUsageCallback();
432
429
  else this.earlyProbeUsage = true;
433
430
  });
434
- this.socket.on("session:finalizeSnapshot", () => {
435
- this.finalizeSnapshotCallback?.();
436
- });
437
431
  this.socket.on("session:runStartCommand", () => {
438
432
  this.runStartCommandCallback?.();
439
433
  });
@@ -714,9 +708,6 @@ var AgentConnection = class _AgentConnection {
714
708
  }).catch(() => {
715
709
  });
716
710
  }
717
- onFinalizeSnapshot(callback) {
718
- this.finalizeSnapshotCallback = callback;
719
- }
720
711
  onRunStartCommand(callback) {
721
712
  this.runStartCommandCallback = callback;
722
713
  }
@@ -1226,55 +1217,51 @@ async function git(cwd, args, timeoutMs = GIT_TIMEOUT_MS) {
1226
1217
  });
1227
1218
  return stdout.toString().trim();
1228
1219
  }
1229
- async function syncWithBaseBranch(cwd, baseBranch) {
1230
- if (!baseBranch) return true;
1220
+ async function ensureOnTaskBranch(cwd, taskBranch, baseBranch) {
1221
+ if (!taskBranch) return true;
1231
1222
  try {
1232
- await git(cwd, ["fetch", "origin", baseBranch]);
1233
- } catch {
1234
- process.stderr.write(
1235
- `[conveyor-agent] Warning: git fetch origin ${baseBranch} failed, continuing with current base
1223
+ if (await getCurrentBranch(cwd) === taskBranch) return true;
1224
+ let existsOnOrigin = true;
1225
+ try {
1226
+ await git(cwd, [
1227
+ "fetch",
1228
+ "origin",
1229
+ `+refs/heads/${taskBranch}:refs/remotes/origin/${taskBranch}`
1230
+ ]);
1231
+ } catch (err) {
1232
+ if (String(err).includes("couldn't find remote ref")) existsOnOrigin = false;
1233
+ else throw err;
1234
+ }
1235
+ if (existsOnOrigin) {
1236
+ await git(cwd, ["checkout", "-B", taskBranch, `origin/${taskBranch}`], 3e4);
1237
+ process.stderr.write(`[conveyor-agent] Checked out task branch ${taskBranch}
1238
+ `);
1239
+ return true;
1240
+ }
1241
+ if (!baseBranch) {
1242
+ process.stderr.write(
1243
+ `[conveyor-agent] Warning: task branch ${taskBranch} missing on origin and no base branch given
1236
1244
  `
1237
- );
1238
- return false;
1239
- }
1240
- try {
1241
- await git(cwd, ["merge", `origin/${baseBranch}`, "--no-edit"], 3e4);
1242
- } catch {
1245
+ );
1246
+ return false;
1247
+ }
1248
+ await git(cwd, [
1249
+ "fetch",
1250
+ "origin",
1251
+ `+refs/heads/${baseBranch}:refs/remotes/origin/${baseBranch}`
1252
+ ]);
1253
+ await git(cwd, ["checkout", "-B", taskBranch, `origin/${baseBranch}`], 3e4);
1254
+ await git(cwd, ["push", "-u", "origin", taskBranch], 3e4);
1243
1255
  process.stderr.write(
1244
- `[conveyor-agent] Warning: merge origin/${baseBranch} failed, aborting merge and continuing
1256
+ `[conveyor-agent] Created task branch ${taskBranch} from origin/${baseBranch} and pushed
1245
1257
  `
1246
1258
  );
1247
- try {
1248
- await git(cwd, ["merge", "--abort"], 3e4);
1249
- } catch {
1250
- }
1251
- return false;
1252
- }
1253
- process.stderr.write(`[conveyor-agent] Synced with latest origin/${baseBranch}
1254
- `);
1255
- return true;
1256
- }
1257
- async function ensureOnTaskBranch(cwd, taskBranch) {
1258
- if (!taskBranch) return true;
1259
- const current = await getCurrentBranch(cwd);
1260
- if (current === taskBranch) return true;
1261
- try {
1262
- await git(cwd, ["fetch", "origin", taskBranch]);
1263
- } catch {
1264
- process.stderr.write(`[conveyor-agent] Warning: git fetch origin ${taskBranch} failed
1265
- `);
1266
- return false;
1267
- }
1268
- try {
1269
- await git(cwd, ["checkout", "-B", taskBranch, `origin/${taskBranch}`], 3e4);
1259
+ return true;
1270
1260
  } catch {
1271
- process.stderr.write(`[conveyor-agent] Warning: git checkout ${taskBranch} failed
1261
+ process.stderr.write(`[conveyor-agent] Warning: ensureOnTaskBranch(${taskBranch}) failed
1272
1262
  `);
1273
1263
  return false;
1274
1264
  }
1275
- process.stderr.write(`[conveyor-agent] Checked out task branch ${taskBranch}
1276
- `);
1277
- return true;
1278
1265
  }
1279
1266
  async function hasUncommittedChanges(cwd) {
1280
1267
  const status = await git(cwd, ["status", "--porcelain"], GIT_SLOW_TIMEOUT_MS);
@@ -1599,206 +1586,6 @@ async function backupOtherBranches(cwd, currentBranch) {
1599
1586
  return count;
1600
1587
  }
1601
1588
 
1602
- // src/runner/work-preservation/index.ts
1603
- import { mkdtemp, rm as rm2 } from "fs/promises";
1604
- import { tmpdir as tmpdir2 } from "os";
1605
- import { join } from "path";
1606
-
1607
- // src/runner/work-preservation/restore-precedence.ts
1608
- function selectRestoreSource(probe) {
1609
- if (probe.gcsAvailable) return "gcs";
1610
- if (probe.wipRestoreResult === "applied") return "wip";
1611
- return "clean";
1612
- }
1613
-
1614
- // src/runner/work-preservation/snapshot-transfer.ts
1615
- import { randomUUID } from "crypto";
1616
- import { createReadStream, createWriteStream } from "fs";
1617
- import { rm } from "fs/promises";
1618
- import { tmpdir } from "os";
1619
- import path from "path";
1620
- import { Readable } from "stream";
1621
- import { pipeline } from "stream/promises";
1622
- var SNAPSHOT_HTTP_TIMEOUT_MS = 6e4;
1623
- var SNAPSHOT_CAPTURED_AT_HEADER = "x-snapshot-captured-at";
1624
- async function putSnapshotToGcs(url, tarResult) {
1625
- try {
1626
- const body = Readable.toWeb(createReadStream(tarResult.tarPath));
1627
- const res = await fetch(url, {
1628
- method: "PUT",
1629
- headers: {
1630
- "Content-Type": "application/gzip",
1631
- "Content-Length": String(tarResult.sizeBytes),
1632
- [SNAPSHOT_CAPTURED_AT_HEADER]: String(tarResult.capturedAt)
1633
- },
1634
- body,
1635
- duplex: "half",
1636
- signal: AbortSignal.timeout(SNAPSHOT_HTTP_TIMEOUT_MS)
1637
- });
1638
- return res.ok;
1639
- } catch {
1640
- return false;
1641
- }
1642
- }
1643
- async function downloadSnapshotToTemp(url) {
1644
- const tmpTar = path.join(tmpdir(), `conveyor-snapshot-restore-${randomUUID()}.tar.gz`);
1645
- try {
1646
- const res = await fetch(url, {
1647
- method: "GET",
1648
- signal: AbortSignal.timeout(SNAPSHOT_HTTP_TIMEOUT_MS)
1649
- });
1650
- if (!res.ok || !res.body) return null;
1651
- await pipeline(
1652
- Readable.fromWeb(res.body),
1653
- createWriteStream(tmpTar)
1654
- );
1655
- return tmpTar;
1656
- } catch {
1657
- await rm(tmpTar, { force: true }).catch(() => {
1658
- });
1659
- return null;
1660
- }
1661
- }
1662
-
1663
- // src/runner/work-preservation/index.ts
1664
- async function captureSnapshotTar(cwd) {
1665
- if (!workbenchEnabled()) return buildSnapshotTar(cwd);
1666
- const staging = await mkdtemp(join(tmpdir2(), "conveyor-snapshot-remote-"));
1667
- const cleanup = async () => {
1668
- await rm2(staging, { recursive: true, force: true }).catch(() => {
1669
- });
1670
- };
1671
- try {
1672
- const meta = await getWorkbenchClient().buildSnapshot(cwd, join(staging, "snapshot.tar.gz"));
1673
- return {
1674
- tarPath: meta.tarPath,
1675
- sizeBytes: meta.sizeBytes,
1676
- fileCount: meta.fileCount,
1677
- deletionCount: meta.deletionCount,
1678
- capturedAt: meta.capturedAt,
1679
- cleanup
1680
- };
1681
- } catch (err) {
1682
- await cleanup();
1683
- throw err;
1684
- }
1685
- }
1686
- function applySnapshotTar(cwd, tarPath) {
1687
- return workbenchEnabled() ? getWorkbenchClient().extractSnapshot(cwd, tarPath) : extractSnapshotTar(cwd, tarPath);
1688
- }
1689
- var periodicTimer = null;
1690
- var inFlightCapture = null;
1691
- var inFlightGcsUpload = null;
1692
- var EMPTY_GCS_RESULT = {
1693
- uploaded: false,
1694
- fileCount: 0,
1695
- deletionCount: 0,
1696
- sizeBytes: 0
1697
- };
1698
- async function doUploadSnapshotToGcs(cwd, uploadUrl, opts) {
1699
- let tarResult;
1700
- try {
1701
- tarResult = await captureSnapshotTar(cwd);
1702
- } catch {
1703
- return EMPTY_GCS_RESULT;
1704
- }
1705
- try {
1706
- const uploaded = (opts?.forceUpload || tarResult.sizeBytes > 0) && uploadUrl ? await putSnapshotToGcs(uploadUrl, tarResult) : false;
1707
- return {
1708
- uploaded,
1709
- fileCount: tarResult.fileCount,
1710
- deletionCount: tarResult.deletionCount,
1711
- sizeBytes: tarResult.sizeBytes
1712
- };
1713
- } finally {
1714
- await tarResult.cleanup();
1715
- }
1716
- }
1717
- async function uploadSnapshotToGcs(cwd, uploadUrl, opts) {
1718
- const previous = inFlightGcsUpload;
1719
- const run = (async () => {
1720
- if (previous) await previous.catch(() => {
1721
- });
1722
- return doUploadSnapshotToGcs(cwd, uploadUrl, opts);
1723
- })();
1724
- inFlightGcsUpload = run;
1725
- try {
1726
- return await run;
1727
- } finally {
1728
- if (inFlightGcsUpload === run) inFlightGcsUpload = null;
1729
- }
1730
- }
1731
- async function captureSnapshot(ctx) {
1732
- const gcs = await uploadSnapshotToGcs(ctx.cwd, ctx.snapshotUploadUrl, {
1733
- forceUpload: ctx.forceUpload
1734
- });
1735
- const wipResult = await flushPendingChanges(ctx.cwd, {
1736
- wipMessage: ctx.wipMessage ?? "WIP: WorkPreservation periodic snapshot",
1737
- refreshToken: ctx.refreshToken
1738
- });
1739
- return {
1740
- fileCount: gcs.fileCount,
1741
- deletionCount: gcs.deletionCount,
1742
- sizeBytes: gcs.sizeBytes,
1743
- gcsUploaded: gcs.uploaded,
1744
- wipPushed: wipResult.committed || wipResult.pushed
1745
- };
1746
- }
1747
- function startPeriodic(intervalMs, ctx) {
1748
- stop();
1749
- periodicTimer = setInterval(() => {
1750
- if (inFlightCapture) return;
1751
- const run = captureSnapshot(ctx);
1752
- inFlightCapture = run;
1753
- void run.catch(() => {
1754
- }).finally(() => {
1755
- if (inFlightCapture === run) inFlightCapture = null;
1756
- });
1757
- }, intervalMs);
1758
- }
1759
- function stop() {
1760
- if (periodicTimer) {
1761
- clearInterval(periodicTimer);
1762
- periodicTimer = null;
1763
- }
1764
- }
1765
- async function finalizeForSleep(ctx) {
1766
- stop();
1767
- if (inFlightCapture) await inFlightCapture.catch(() => {
1768
- });
1769
- return captureSnapshot({
1770
- ...ctx,
1771
- wipMessage: ctx.wipMessage ?? "WIP: WorkPreservation sleep finalize snapshot",
1772
- // Always stamp snapshotAt on sleep — even an empty workspace must confirm
1773
- // the finalize so teardown doesn't wait out the reconciler's cap.
1774
- forceUpload: true
1775
- });
1776
- }
1777
- async function restoreOnBoot(bundle, cwd) {
1778
- let gcsAvailable = false;
1779
- let gcsFileCount;
1780
- if (bundle.snapshotUrl) {
1781
- const tmpTar = await downloadSnapshotToTemp(bundle.snapshotUrl);
1782
- if (tmpTar) {
1783
- try {
1784
- const result = await applySnapshotTar(cwd, tmpTar);
1785
- if (result.status === "extracted") {
1786
- gcsAvailable = true;
1787
- gcsFileCount = result.filesExtracted;
1788
- }
1789
- } catch {
1790
- gcsAvailable = false;
1791
- } finally {
1792
- await rm2(tmpTar, { force: true }).catch(() => {
1793
- });
1794
- }
1795
- }
1796
- }
1797
- const wipRestoreResult = gcsAvailable ? "none" : await restoreWipSnapshot(cwd, bundle.gitPlan.branch);
1798
- const source = selectRestoreSource({ gcsAvailable, wipRestoreResult });
1799
- return source === "gcs" ? { source, fileCount: gcsFileCount } : { source };
1800
- }
1801
-
1802
1589
  // ../shared/dist/index.js
1803
1590
  import { z } from "zod";
1804
1591
  import { z as z2 } from "zod";
@@ -3306,9 +3093,9 @@ var ClaudeCodeHarness = class {
3306
3093
  };
3307
3094
 
3308
3095
  // src/harness/pty/session.ts
3309
- import { randomUUID as randomUUID2 } from "crypto";
3310
- import { mkdtemp as mkdtemp2, mkdir as mkdir3, rm as rm4 } from "fs/promises";
3311
- import { join as join5, dirname } from "path";
3096
+ import { randomUUID } from "crypto";
3097
+ import { mkdtemp, mkdir as mkdir3, rm as rm2 } from "fs/promises";
3098
+ import { join as join4, dirname } from "path";
3312
3099
 
3313
3100
  // src/harness/pty/event-queue.ts
3314
3101
  var AsyncEventQueue = class {
@@ -3589,8 +3376,8 @@ function mapTranscriptRecord(raw) {
3589
3376
  // src/harness/pty/jsonl-tailer.ts
3590
3377
  var POLL_INTERVAL_MS = 25;
3591
3378
  var JsonlTailer = class {
3592
- constructor(path3, onEvent, onRawRecord) {
3593
- this.path = path3;
3379
+ constructor(path2, onEvent, onRawRecord) {
3380
+ this.path = path2;
3594
3381
  this.onEvent = onEvent;
3595
3382
  this.onRawRecord = onRawRecord;
3596
3383
  }
@@ -3852,15 +3639,15 @@ function mapChatRecords(raw) {
3852
3639
  // src/harness/pty/settings.ts
3853
3640
  import { mkdir, writeFile, chmod } from "fs/promises";
3854
3641
  import { homedir } from "os";
3855
- import { join as join2 } from "path";
3642
+ import { join } from "path";
3856
3643
  function claudeConfigHome() {
3857
- return process.env.CLAUDE_CONFIG_DIR ?? join2(homedir(), ".claude");
3644
+ return process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude");
3858
3645
  }
3859
3646
  function projectSlug(cwd) {
3860
3647
  return cwd.replace(/\//g, "-");
3861
3648
  }
3862
3649
  function sessionTranscriptPath(cwd, sessionId) {
3863
- return join2(claudeConfigHome(), "projects", projectSlug(cwd), `${sessionId}.jsonl`);
3650
+ return join(claudeConfigHome(), "projects", projectSlug(cwd), `${sessionId}.jsonl`);
3864
3651
  }
3865
3652
  var ALLOW_RULES = [
3866
3653
  "Bash",
@@ -4028,8 +3815,8 @@ function preToolUse(payload) {
4028
3815
  }
4029
3816
  `;
4030
3817
  async function writeHookSettings(dir) {
4031
- const helperPath = join2(dir, "hook-helper.cjs");
4032
- const settingsPath = join2(dir, "settings.json");
3818
+ const helperPath = join(dir, "hook-helper.cjs");
3819
+ const settingsPath = join(dir, "settings.json");
4033
3820
  await mkdir(dir, { recursive: true });
4034
3821
  await writeFile(helperPath, HOOK_HELPER_SOURCE, "utf8");
4035
3822
  await chmod(helperPath, 493);
@@ -4147,7 +3934,7 @@ var PtyOutputCoalescer = class {
4147
3934
  import { createServer as createServer2 } from "http";
4148
3935
  import { z as z6 } from "zod";
4149
3936
  import { writeFile as writeFile2 } from "fs/promises";
4150
- import { join as join3 } from "path";
3937
+ import { join as join2 } from "path";
4151
3938
  import { randomBytes } from "crypto";
4152
3939
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4153
3940
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
@@ -4338,7 +4125,7 @@ async function startToolServers(mcpServers, tempDir) {
4338
4125
  config[name] = { type: "http", url, headers: { Authorization: `Bearer ${token}` } };
4339
4126
  }
4340
4127
  if (Object.keys(config).length === 0) return { servers, mcpConfigPath: null };
4341
- const mcpConfigPath = join3(tempDir, "mcp-config.json");
4128
+ const mcpConfigPath = join2(tempDir, "mcp-config.json");
4342
4129
  await writeFile2(mcpConfigPath, JSON.stringify({ mcpServers: config }, null, 2), "utf8");
4343
4130
  return { servers, mcpConfigPath };
4344
4131
  }
@@ -4414,13 +4201,13 @@ ${tail}`);
4414
4201
  }
4415
4202
 
4416
4203
  // src/harness/pty/credentials.ts
4417
- import { chmod as chmod2, mkdir as mkdir2, readFile, rm as rm3, writeFile as writeFile3 } from "fs/promises";
4204
+ import { chmod as chmod2, mkdir as mkdir2, readFile, rm, writeFile as writeFile3 } from "fs/promises";
4418
4205
  import { homedir as homedir2 } from "os";
4419
- import { join as join4 } from "path";
4206
+ import { join as join3 } from "path";
4420
4207
  var SYNTH_TOKEN_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
4421
4208
  var REFRESH_SKEW_MS = 30 * 24 * 60 * 60 * 1e3;
4422
4209
  function claudeCredentialsPath() {
4423
- return join4(claudeConfigHome(), ".credentials.json");
4210
+ return join3(claudeConfigHome(), ".credentials.json");
4424
4211
  }
4425
4212
  function isConveyorCloudEnv(env = process.env) {
4426
4213
  return Boolean(env.CLAUDESPACE_NAME || env.CODESPACE_NAME || env.CODESPACES);
@@ -4465,9 +4252,9 @@ function planCredentialsWrite(input) {
4465
4252
  if (fresh) return { action: "skip", reason: "current" };
4466
4253
  return { action: "write", contents };
4467
4254
  }
4468
- async function readRaw(path3) {
4255
+ async function readRaw(path2) {
4469
4256
  try {
4470
- return await readFile(path3, "utf8");
4257
+ return await readFile(path2, "utf8");
4471
4258
  } catch {
4472
4259
  return null;
4473
4260
  }
@@ -4509,10 +4296,10 @@ async function writeWithReadBackRetry(io2, contents, delaysMs = READ_BACK_DELAYS
4509
4296
  await sleep2(delaysMs[attempt]);
4510
4297
  }
4511
4298
  }
4512
- function fsWriteIo(path3, mode) {
4299
+ function fsWriteIo(path2, mode) {
4513
4300
  return {
4514
- write: (contents) => writeFile3(path3, contents, mode === void 0 ? "utf8" : { encoding: "utf8", mode }),
4515
- read: () => readRaw(path3)
4301
+ write: (contents) => writeFile3(path2, contents, mode === void 0 ? "utf8" : { encoding: "utf8", mode }),
4302
+ read: () => readRaw(path2)
4516
4303
  };
4517
4304
  }
4518
4305
  async function ensureClaudeCredentials(env = process.env) {
@@ -4522,23 +4309,23 @@ async function ensureClaudeCredentials(env = process.env) {
4522
4309
  await sanitizeApprovedApiKeys(token);
4523
4310
  }
4524
4311
  try {
4525
- const path3 = claudeCredentialsPath();
4312
+ const path2 = claudeCredentialsPath();
4526
4313
  const plan = planCredentialsWrite({
4527
4314
  isCloud,
4528
4315
  token,
4529
- existingRaw: await readRaw(path3),
4316
+ existingRaw: await readRaw(path2),
4530
4317
  now: Date.now()
4531
4318
  });
4532
4319
  if (plan.action === "skip") return;
4533
4320
  await mkdir2(claudeConfigHome(), { recursive: true });
4534
- const verified = await writeWithReadBackRetry(fsWriteIo(path3, 384), plan.contents);
4321
+ const verified = await writeWithReadBackRetry(fsWriteIo(path2, 384), plan.contents);
4535
4322
  if (!verified) {
4536
4323
  process.stderr.write(
4537
- `[conveyor-agent] claude credentials read-back still stale after retries at ${path3} \u2014 TUI may land on the login picker
4324
+ `[conveyor-agent] claude credentials read-back still stale after retries at ${path2} \u2014 TUI may land on the login picker
4538
4325
  `
4539
4326
  );
4540
4327
  }
4541
- await chmod2(path3, 384).catch(() => {
4328
+ await chmod2(path2, 384).catch(() => {
4542
4329
  });
4543
4330
  } catch (err) {
4544
4331
  const message = err instanceof Error ? err.message : String(err);
@@ -4567,12 +4354,12 @@ function planApprovedApiKeyCleanup(existingRaw, oauthToken) {
4567
4354
  }
4568
4355
  async function sanitizeApprovedApiKeys(oauthToken) {
4569
4356
  try {
4570
- const path3 = claudeJsonPath();
4571
- const cleaned = planApprovedApiKeyCleanup(await readRaw(path3), oauthToken);
4357
+ const path2 = claudeJsonPath();
4358
+ const cleaned = planApprovedApiKeyCleanup(await readRaw(path2), oauthToken);
4572
4359
  if (cleaned === null) return;
4573
- const verified = await writeWithReadBackRetry(fsWriteIo(path3), cleaned);
4360
+ const verified = await writeWithReadBackRetry(fsWriteIo(path2), cleaned);
4574
4361
  process.stderr.write(
4575
- verified ? "[conveyor-agent] removed poisoned customApiKeyResponses.approved entry from .claude.json\n" : `[conveyor-agent] approved-key sanitize read-back still stale after retries at ${path3} \u2014 CLI may still see the poisoned entry
4362
+ verified ? "[conveyor-agent] removed poisoned customApiKeyResponses.approved entry from .claude.json\n" : `[conveyor-agent] approved-key sanitize read-back still stale after retries at ${path2} \u2014 CLI may still see the poisoned entry
4576
4363
  `
4577
4364
  );
4578
4365
  } catch (err) {
@@ -4583,7 +4370,7 @@ async function sanitizeApprovedApiKeys(oauthToken) {
4583
4370
  }
4584
4371
  function claudeJsonPath() {
4585
4372
  const configDir = process.env.CLAUDE_CONFIG_DIR;
4586
- return configDir ? join4(configDir, ".claude.json") : join4(homedir2(), ".claude.json");
4373
+ return configDir ? join3(configDir, ".claude.json") : join3(homedir2(), ".claude.json");
4587
4374
  }
4588
4375
  function asRecord(value) {
4589
4376
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
@@ -4676,7 +4463,7 @@ function planClaudeJsonSeed(existingRaw, trustCwd, oauthIdentity) {
4676
4463
  return changed ? JSON.stringify(config) : null;
4677
4464
  }
4678
4465
  function conveyorOauthMarkerPath() {
4679
- return join4(claudeConfigHome(), "conveyor-oauth-account.json");
4466
+ return join3(claudeConfigHome(), "conveyor-oauth-account.json");
4680
4467
  }
4681
4468
  function parseOauthIdentity(raw) {
4682
4469
  if (!raw || raw.trim() === "") return null;
@@ -4709,8 +4496,8 @@ async function persistOauthIdentityMarker(configIdentity, markerIdentity) {
4709
4496
  async function ensureClaudeOnboarding(env = process.env, trustCwd) {
4710
4497
  try {
4711
4498
  if (!isConveyorCloudEnv(env)) return;
4712
- const path3 = claudeJsonPath();
4713
- const existingRaw = await readRaw(path3);
4499
+ const path2 = claudeJsonPath();
4500
+ const existingRaw = await readRaw(path2);
4714
4501
  const markerIdentity = parseOauthIdentity(await readRaw(conveyorOauthMarkerPath()));
4715
4502
  await persistOauthIdentityMarker(
4716
4503
  extractOauthIdentity(parseClaudeJson(existingRaw)),
@@ -4718,16 +4505,16 @@ async function ensureClaudeOnboarding(env = process.env, trustCwd) {
4718
4505
  );
4719
4506
  const contents = planClaudeJsonSeed(existingRaw, trustCwd, markerIdentity);
4720
4507
  if (contents === null) return;
4721
- const verified = await writeWithReadBackRetry(fsWriteIo(path3), contents);
4508
+ const verified = await writeWithReadBackRetry(fsWriteIo(path2), contents);
4722
4509
  if (verified) {
4723
4510
  process.stderr.write(
4724
4511
  `[conveyor-agent] claude onboarding seeded${trustCwd ? ` (trust: ${trustCwd})` : ""}
4725
4512
  `
4726
4513
  );
4727
4514
  } else {
4728
- const verify = await readRaw(path3);
4515
+ const verify = await readRaw(path2);
4729
4516
  process.stderr.write(
4730
- `[conveyor-agent] claude onboarding seed read-back MISMATCH at ${path3} after retries: wrote ${contents.length}B, read ${verify?.length ?? 0}B \u2014 CLI may see stale config and park at a startup dialog
4517
+ `[conveyor-agent] claude onboarding seed read-back MISMATCH at ${path2} after retries: wrote ${contents.length}B, read ${verify?.length ?? 0}B \u2014 CLI may see stale config and park at a startup dialog
4731
4518
  `
4732
4519
  );
4733
4520
  }
@@ -4740,12 +4527,12 @@ async function ensureClaudeOnboarding(env = process.env, trustCwd) {
4740
4527
  async function removeConveyorCredentials(env = process.env) {
4741
4528
  try {
4742
4529
  if (!isConveyorCloudEnv(env)) return;
4743
- const path3 = claudeCredentialsPath();
4744
- const existing = parseClaudeAiOauth(await readRaw(path3));
4530
+ const path2 = claudeCredentialsPath();
4531
+ const existing = parseClaudeAiOauth(await readRaw(path2));
4745
4532
  if (existing && typeof existing.refreshToken === "string" && existing.refreshToken.length > 0) {
4746
4533
  return;
4747
4534
  }
4748
- await rm3(path3, { force: true });
4535
+ await rm(path2, { force: true });
4749
4536
  } catch (err) {
4750
4537
  const message = err instanceof Error ? err.message : String(err);
4751
4538
  process.stderr.write(`[conveyor-agent] claude credentials removal failed: ${message}
@@ -5053,7 +4840,7 @@ var PtySession = class {
5053
4840
  const { settingsPath, socketPath } = await this.startStructuredEventSources(sessionId);
5054
4841
  await this.spawn(settingsPath, socketPath);
5055
4842
  } else {
5056
- this.tempDir = await mkdtemp2(join5(sessionTempBase(), "conveyor-pty-"));
4843
+ this.tempDir = await mkdtemp(join4(sessionTempBase(), "conveyor-pty-"));
5057
4844
  await this.spawn();
5058
4845
  this.pushEvent({
5059
4846
  type: "system",
@@ -5090,8 +4877,8 @@ var PtySession = class {
5090
4877
  * paths spawn() must wire into the child's argv/env.
5091
4878
  */
5092
4879
  async startStructuredEventSources(sessionId) {
5093
- this.tempDir = await mkdtemp2(join5(sessionTempBase(), "conveyor-pty-"));
5094
- const socketPath = join5(this.tempDir, "hook.sock");
4880
+ this.tempDir = await mkdtemp(join4(sessionTempBase(), "conveyor-pty-"));
4881
+ const socketPath = join4(this.tempDir, "hook.sock");
5095
4882
  this.socket = new HookSocketServer(
5096
4883
  socketPath,
5097
4884
  (progress) => this.handleProgress(progress),
@@ -5145,7 +4932,7 @@ var PtySession = class {
5145
4932
  * questionnaire resolves; see the field comment). */
5146
4933
  emitSyntheticQuestionCard(questions) {
5147
4934
  if (questions.length === 0 || typeof this.bridge?.sendChatEvent !== "function") return;
5148
- const id = `aq-${randomUUID2()}`;
4935
+ const id = `aq-${randomUUID()}`;
5149
4936
  this.pendingSyntheticQuestionIds.push(id);
5150
4937
  this.sendChatEvent({
5151
4938
  kind: "tool_use",
@@ -5268,7 +5055,7 @@ var PtySession = class {
5268
5055
  this.activeQueue?.close();
5269
5056
  this.activeQueue = null;
5270
5057
  if (this.tempDir) {
5271
- await rm4(this.tempDir, { recursive: true, force: true });
5058
+ await rm2(this.tempDir, { recursive: true, force: true });
5272
5059
  this.tempDir = "";
5273
5060
  }
5274
5061
  }
@@ -5544,7 +5331,7 @@ var PtySession = class {
5544
5331
  // src/harness/pty/config-home-health.ts
5545
5332
  import { mkdir as mkdir4 } from "fs/promises";
5546
5333
  import { homedir as homedir3 } from "os";
5547
- import { join as join6 } from "path";
5334
+ import { join as join5 } from "path";
5548
5335
  var MOUNT_DISCONNECT_CODES = /* @__PURE__ */ new Set(["ENOTCONN", "EIO", "ESTALE", "ENXIO"]);
5549
5336
  var MOUNT_DISCONNECT_MESSAGES = [
5550
5337
  "socket is not connected",
@@ -5560,12 +5347,12 @@ function isMountDisconnectError(err) {
5560
5347
  return MOUNT_DISCONNECT_MESSAGES.some((needle) => lower.includes(needle));
5561
5348
  }
5562
5349
  function podLocalConfigHome() {
5563
- return join6(homedir3(), ".claude-local");
5350
+ return join5(homedir3(), ".claude-local");
5564
5351
  }
5565
5352
  async function ensureUsableClaudeConfigHome(cwd, log) {
5566
5353
  const configHome = claudeConfigHome();
5567
5354
  try {
5568
- await mkdir4(join6(configHome, "projects", projectSlug(cwd)), { recursive: true });
5355
+ await mkdir4(join5(configHome, "projects", projectSlug(cwd)), { recursive: true });
5569
5356
  return { configHome, fellBack: false };
5570
5357
  } catch (err) {
5571
5358
  if (!isMountDisconnectError(err)) throw err;
@@ -5579,7 +5366,7 @@ async function ensureUsableClaudeConfigHome(cwd, log) {
5579
5366
  }
5580
5367
  );
5581
5368
  process.env.CLAUDE_CONFIG_DIR = fallback;
5582
- await mkdir4(join6(fallback, "projects", projectSlug(cwd)), { recursive: true });
5369
+ await mkdir4(join5(fallback, "projects", projectSlug(cwd)), { recursive: true });
5583
5370
  return { configHome: fallback, fellBack: true };
5584
5371
  }
5585
5372
  }
@@ -6066,20 +5853,20 @@ function formatIncidents(incidents) {
6066
5853
 
6067
5854
  // src/workbench/fs.ts
6068
5855
  import { readFile as localReadFile, readdir as localReaddir, stat as localStat } from "fs/promises";
6069
- async function readWorkspaceFile(path3) {
5856
+ async function readWorkspaceFile(path2) {
6070
5857
  if (workbenchEnabled()) {
6071
- return (await getWorkbenchClient().readFile(path3)).toString("utf8");
5858
+ return (await getWorkbenchClient().readFile(path2)).toString("utf8");
6072
5859
  }
6073
- return localReadFile(path3, "utf-8");
5860
+ return localReadFile(path2, "utf-8");
6074
5861
  }
6075
- function readWorkspaceDir(path3) {
6076
- if (workbenchEnabled()) return getWorkbenchClient().readdir(path3);
6077
- return localReaddir(path3);
5862
+ function readWorkspaceDir(path2) {
5863
+ if (workbenchEnabled()) return getWorkbenchClient().readdir(path2);
5864
+ return localReaddir(path2);
6078
5865
  }
6079
- async function statWorkspacePath(path3) {
6080
- if (workbenchEnabled()) return getWorkbenchClient().stat(path3);
5866
+ async function statWorkspacePath(path2) {
5867
+ if (workbenchEnabled()) return getWorkbenchClient().stat(path2);
6081
5868
  try {
6082
- const s = await localStat(path3);
5869
+ const s = await localStat(path2);
6083
5870
  return {
6084
5871
  exists: true,
6085
5872
  isFile: s.isFile(),
@@ -6091,8 +5878,8 @@ async function statWorkspacePath(path3) {
6091
5878
  return { exists: false, isFile: false, isDirectory: false, size: 0, mtimeMs: 0 };
6092
5879
  }
6093
5880
  }
6094
- async function workspacePathExists(path3) {
6095
- return (await statWorkspacePath(path3)).exists;
5881
+ async function workspacePathExists(path2) {
5882
+ return (await statWorkspacePath(path2)).exists;
6096
5883
  }
6097
5884
 
6098
5885
  // src/execution/tag-context-resolver.ts
@@ -6463,13 +6250,15 @@ function buildPlanDocumentationSection(context) {
6463
6250
  const hasPlan = !!context?.plan?.trim();
6464
6251
  return [
6465
6252
  ``,
6466
- `### Plan & Properties (post the plan BEFORE building \u2014 no approval wait)`,
6253
+ `### Plan first, then BUILD \u2014 the plan is a step, NOT the deliverable`,
6467
6254
  `- The card is already In Progress and advances automatically (In Progress \u2192 Review PR when you open the PR). There is no plan-approval step.`,
6468
6255
  ...hasPlan ? [
6469
- `- A plan is already saved on the card. Keep it current with update_task_plan if your approach diverges materially from it.`
6256
+ `- A plan is already saved on the card. Keep it current with update_task_plan if your approach diverges materially \u2014 then IMPLEMENT it in code. The saved plan is not the deliverable; the working implementation is.`
6470
6257
  ] : [
6471
- `- No plan is saved yet: BEFORE writing any code, investigate briefly (search first, read only critical files) and save a concise implementation plan with update_task_plan (file:line citations). Then start implementing immediately \u2014 the plan is a record for the team, not a gate; never pause for approval.`
6258
+ `- No plan is saved yet: BEFORE writing any code, investigate briefly (search first, read only critical files) and save a concise implementation plan with update_task_plan (file:line citations). Saving the plan is a planning step, not the goal \u2014 immediately move on to WRITING THE CODE that implements it; never pause for approval.`
6472
6259
  ],
6260
+ `- Your goal is to BUILD the change, not to produce a plan. After the plan is posted, actually implement it: edit source files, make the change work, then verify. Do NOT stop, go idle, or open a PR the moment the plan exists.`,
6261
+ `- Your pull request MUST contain the actual code implementation. Never open a plan-only or empty-diff PR \u2014 a PR that just records the plan is never the goal of auto mode unless the task explicitly asks ONLY for a plan. (If the task genuinely needs no code changes, don't open a PR at all \u2014 finish per the section below.)`,
6473
6262
  `- Identification auto-fills title, story points, and icon with quick AI guesses. After exploring, refine the title and story points with update_task_properties if they look like placeholders. Icons are automatic \u2014 never set them.`
6474
6263
  ];
6475
6264
  }
@@ -7115,7 +6904,7 @@ function buildFreshLeafInstructions(context, isAutoMode) {
7115
6904
  `Your FIRST action should be reading the relevant source files mentioned in the plan, then writing code. Do NOT run install, build, lint, test, or dev server commands first \u2014 the environment is already set up.`
7116
6905
  ] : [
7117
6906
  `No plan is saved on this card yet. Investigate the task briefly (search first, read only critical files), then save a concise implementation plan with update_task_plan (file:line citations) BEFORE you start writing code.`,
7118
- `Once the plan is posted, implement it immediately \u2014 do NOT pause or wait for approval; the card is already In Progress and advances automatically.`,
6907
+ `The plan is a step, not the goal: the moment it's posted, IMPLEMENT it in code \u2014 do NOT pause, wait for approval, or treat the posted plan as the deliverable. The card is already In Progress and advances automatically.`,
7119
6908
  `Do NOT run install, build, lint, test, or dev server commands first \u2014 the environment is already set up.`
7120
6909
  ];
7121
6910
  parts.push(
@@ -7127,9 +6916,10 @@ function buildFreshLeafInstructions(context, isAutoMode) {
7127
6916
  if (isAutoMode) {
7128
6917
  parts.push(
7129
6918
  `
7130
- CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or go idle without making code changes.`,
7131
- `Do NOT summarize the plan or say "ready to implement" \u2014 start implementing immediately.`,
7132
- `When all changes are ready, you MUST use mcp__conveyor__create_pull_request to open a PR before finishing.`,
6919
+ CRITICAL: You are in Auto mode. Your job is to BUILD the change, not to produce a plan \u2014 making a plan and opening a PR of the plan is NOT the goal. The plan is only an intermediate step; you must then write the code that implements it.`,
6920
+ `Do NOT report status, ask for confirmation, or go idle without making code changes. Do NOT summarize the plan or say "ready to implement" \u2014 start implementing immediately.`,
6921
+ `Your pull request MUST contain the actual code implementation. Never open a plan-only or empty-diff PR. (If the task genuinely needs no code changes, do not open a PR \u2014 deliver the result in chat and mark the card Complete.)`,
6922
+ `When the implementation is complete and verified, you MUST use mcp__conveyor__create_pull_request to open a PR before finishing.`,
7133
6923
  `If you are genuinely blocked, explain the specific blocker \u2014 do not go idle silently.`
7134
6924
  );
7135
6925
  }
@@ -7868,7 +7658,7 @@ function buildMutationTools(connection, config) {
7868
7658
 
7869
7659
  // src/tools/attachment-tools.ts
7870
7660
  import { readFile as readFile2, stat } from "fs/promises";
7871
- import { basename, extname, isAbsolute, join as join7 } from "path";
7661
+ import { basename, extname, isAbsolute, join as join6 } from "path";
7872
7662
  import { z as z10 } from "zod";
7873
7663
  var IMAGE_MIME_BY_EXT = {
7874
7664
  ".png": "image/png",
@@ -7885,9 +7675,9 @@ function buildUploadAttachmentTool(connection, config) {
7885
7675
  path: z10.string().describe("Path to the image file \u2014 absolute, or relative to the workspace root"),
7886
7676
  title: z10.string().optional().describe("Short caption posted with the image (defaults to the file name)")
7887
7677
  },
7888
- async ({ path: path3, title }) => {
7678
+ async ({ path: path2, title }) => {
7889
7679
  try {
7890
- const filePath = isAbsolute(path3) ? path3 : join7(config.workspaceDir, path3);
7680
+ const filePath = isAbsolute(path2) ? path2 : join6(config.workspaceDir, path2);
7891
7681
  const mimeType = IMAGE_MIME_BY_EXT[extname(filePath).toLowerCase()];
7892
7682
  if (!mimeType) {
7893
7683
  return textResult(
@@ -8649,7 +8439,7 @@ function createConveyorMcpServer(harness, connection, config, context, agentMode
8649
8439
 
8650
8440
  // src/harness/pty/adapters/types.ts
8651
8441
  import { accessSync, constants, statSync } from "fs";
8652
- import { join as join8 } from "path";
8442
+ import { join as join7 } from "path";
8653
8443
  var TuiUnavailableError = class extends Error {
8654
8444
  constructor(tui, message) {
8655
8445
  super(message);
@@ -8658,10 +8448,10 @@ var TuiUnavailableError = class extends Error {
8658
8448
  }
8659
8449
  tui;
8660
8450
  };
8661
- function isExecutable(path3) {
8451
+ function isExecutable(path2) {
8662
8452
  try {
8663
- if (!statSync(path3).isFile()) return false;
8664
- accessSync(path3, constants.X_OK);
8453
+ if (!statSync(path2).isFile()) return false;
8454
+ accessSync(path2, constants.X_OK);
8665
8455
  return true;
8666
8456
  } catch {
8667
8457
  return false;
@@ -8673,7 +8463,7 @@ function findOnPath(binary, env = process.env) {
8673
8463
  }
8674
8464
  for (const dir of (env.PATH ?? "").split(":")) {
8675
8465
  if (!dir) continue;
8676
- const candidate = join8(dir, binary);
8466
+ const candidate = join7(dir, binary);
8677
8467
  if (isExecutable(candidate)) return candidate;
8678
8468
  }
8679
8469
  return null;
@@ -9537,10 +9327,10 @@ function resolveSessionStart(lineageKey, cwd) {
9537
9327
  }
9538
9328
  return { sessionId: sessionUuid };
9539
9329
  }
9540
- function repairTornSessionFile(path3) {
9330
+ function repairTornSessionFile(path2) {
9541
9331
  try {
9542
- if (!existsSync2(path3)) return false;
9543
- const content = readFileSync(path3, "utf8");
9332
+ if (!existsSync2(path2)) return false;
9333
+ const content = readFileSync(path2, "utf8");
9544
9334
  if (content.length === 0) return false;
9545
9335
  let keepEnd = content.length;
9546
9336
  if (!content.endsWith("\n")) {
@@ -9559,9 +9349,9 @@ function repairTornSessionFile(path3) {
9559
9349
  keepEnd = prevNewline + 1;
9560
9350
  }
9561
9351
  if (keepEnd === content.length) return false;
9562
- truncateSync(path3, Buffer.byteLength(content.slice(0, keepEnd), "utf8"));
9352
+ truncateSync(path2, Buffer.byteLength(content.slice(0, keepEnd), "utf8"));
9563
9353
  logger2.warn("Repaired torn transcript before resume", {
9564
- path: path3,
9354
+ path: path2,
9565
9355
  trimmedBytes: content.length - keepEnd
9566
9356
  });
9567
9357
  return true;
@@ -10370,7 +10160,7 @@ var QueryBridge = class {
10370
10160
 
10371
10161
  // src/runner/session-runner-helpers.ts
10372
10162
  import { readFileSync as readFileSync2 } from "fs";
10373
- import { dirname as dirname2, join as join9 } from "path";
10163
+ import { dirname as dirname2, join as join8 } from "path";
10374
10164
  import { fileURLToPath as fileURLToPath2 } from "url";
10375
10165
  function mapChatHistory(messages) {
10376
10166
  if (!messages) return [];
@@ -10399,7 +10189,7 @@ function readAgentVersion() {
10399
10189
  const here = dirname2(fileURLToPath2(import.meta.url));
10400
10190
  for (const rel of ["../package.json", "../../package.json"]) {
10401
10191
  try {
10402
- const pkg = JSON.parse(readFileSync2(join9(here, rel), "utf-8"));
10192
+ const pkg = JSON.parse(readFileSync2(join8(here, rel), "utf-8"));
10403
10193
  if (pkg.version) return pkg.version;
10404
10194
  } catch {
10405
10195
  }
@@ -10702,10 +10492,10 @@ import { access, stat as stat2 } from "fs/promises";
10702
10492
  var DEFAULT_FAILED_PATH = "/workspaces/.conveyor-git-failed";
10703
10493
  var DEFAULT_TIMEOUT_MS = 6e5;
10704
10494
  var DEFAULT_POLL_MS = 200;
10705
- async function fileExists(path3) {
10495
+ async function fileExists(path2) {
10706
10496
  try {
10707
- await access(path3);
10708
- const s = await stat2(path3);
10497
+ await access(path2);
10498
+ const s = await stat2(path2);
10709
10499
  return s.isFile();
10710
10500
  } catch {
10711
10501
  return false;
@@ -10820,9 +10610,9 @@ var DEFAULT_PROC_PATHS = ["/proc/net/tcp", "/proc/net/tcp6"];
10820
10610
  async function readProcListeningPorts(procPaths = DEFAULT_PROC_PATHS) {
10821
10611
  const sockets = [];
10822
10612
  let readable = false;
10823
- for (const path3 of procPaths) {
10613
+ for (const path2 of procPaths) {
10824
10614
  try {
10825
- const content = await readFile3(path3, "utf8");
10615
+ const content = await readFile3(path2, "utf8");
10826
10616
  readable = true;
10827
10617
  sockets.push(...parseProcNetTcpListeners(content));
10828
10618
  } catch {
@@ -11049,7 +10839,7 @@ async function handlePullBranch(workDir, branch) {
11049
10839
 
11050
10840
  // src/runner/heavy-gate.ts
11051
10841
  import { readFileSync as readFileSync3 } from "fs";
11052
- import path2 from "path";
10842
+ import path from "path";
11053
10843
  var GATE_KEYS = ["heavy", "test", "typecheck", "build"];
11054
10844
  function runDir() {
11055
10845
  return process.env.CONVEYOR_RUN_DIR ?? "/tmp/conveyor-run";
@@ -11065,7 +10855,7 @@ function pidAlive(pid) {
11065
10855
  function isHeavyGateActive() {
11066
10856
  for (const key of GATE_KEYS) {
11067
10857
  try {
11068
- const raw = readFileSync3(path2.join(runDir(), `${key}.pid`), "utf8").trim();
10858
+ const raw = readFileSync3(path.join(runDir(), `${key}.pid`), "utf8").trim();
11069
10859
  const pid = Number.parseInt(raw, 10);
11070
10860
  if (Number.isInteger(pid) && pid > 0 && pidAlive(pid)) return true;
11071
10861
  } catch {
@@ -11110,6 +10900,12 @@ var SessionRunner = class _SessionRunner {
11110
10900
  portDiscovery;
11111
10901
  /** Main event-loop lag measurement, shared with the heartbeat worker. */
11112
10902
  loopLag = new LoopLagMonitor();
10903
+ /** Boot supervisor handle, set post-construction once it's known (it is
10904
+ * started alongside connect(), before the runner itself exists in some
10905
+ * call sites) — notified when the core loop goes live so it can release
10906
+ * the app's start command. Optional: never set in tests/paths that don't
10907
+ * wire a supervisor. */
10908
+ workspaceCommands = null;
11113
10909
  constructor(config, callbacks, deps = {}) {
11114
10910
  this.config = config;
11115
10911
  this.callbacks = callbacks;
@@ -11156,6 +10952,12 @@ var SessionRunner = class _SessionRunner {
11156
10952
  get isStopped() {
11157
10953
  return this.stopped;
11158
10954
  }
10955
+ /** Wire the boot supervisor handle post-construction — cli.ts constructs it
10956
+ * via startWorkspaceCommandsAfterConnect() (which needs connect() to already
10957
+ * be callable), so it can't be a constructor dependency. */
10958
+ setWorkspaceCommands(supervisor) {
10959
+ this.workspaceCommands = supervisor;
10960
+ }
11159
10961
  // ── Main lifecycle ─────────────────────────────────────────────────
11160
10962
  /**
11161
10963
  * Establish the API connection, wire callbacks, and join the session room.
@@ -11233,39 +11035,27 @@ var SessionRunner = class _SessionRunner {
11233
11035
  await this.shutdown("error");
11234
11036
  return;
11235
11037
  }
11236
- if (process.env.CONVEYOR_GIT_READY !== "1") {
11237
- if (this.fullContext?.githubBranch) {
11238
- await ensureOnTaskBranch(this.config.workspaceDir, this.fullContext.githubBranch);
11239
- }
11240
- if (this.fullContext?.baseBranch) {
11241
- await syncWithBaseBranch(this.config.workspaceDir, this.fullContext.baseBranch);
11038
+ if (this.fullContext?.githubBranch) {
11039
+ const ok = await ensureOnTaskBranch(
11040
+ this.config.workspaceDir,
11041
+ this.fullContext.githubBranch,
11042
+ this.fullContext.baseBranch ?? void 0
11043
+ );
11044
+ if (!ok) {
11045
+ process.stderr.write("[conveyor-agent] WARNING: task-branch checkout failed\n");
11242
11046
  }
11243
11047
  }
11244
11048
  if (!this.stopped) {
11245
11049
  this.lifecycle.startGitFlush();
11246
11050
  }
11247
11051
  if (this.fullContext?.githubBranch) {
11248
- const snapshotUrl = process.env.CONVEYOR_SNAPSHOT_URL;
11249
- if (snapshotUrl) {
11250
- const restore = await restoreOnBoot(
11251
- { snapshotUrl, gitPlan: { branch: this.fullContext.githubBranch } },
11252
- this.config.workspaceDir
11253
- );
11254
- if (restore.source !== "clean") {
11255
- process.stderr.write(
11256
- `[conveyor-agent] WorkPreservation restore: source=${restore.source}${restore.fileCount === void 0 ? "" : ` files=${restore.fileCount}`}
11257
- `
11258
- );
11259
- }
11260
- } else {
11261
- const restored = await restoreWipSnapshot(
11262
- this.config.workspaceDir,
11263
- this.fullContext.githubBranch
11264
- );
11265
- if (restored !== "none") {
11266
- process.stderr.write(`[conveyor-agent] WIP snapshot restore: ${restored}
11052
+ const restored = await restoreWipSnapshot(
11053
+ this.config.workspaceDir,
11054
+ this.fullContext.githubBranch
11055
+ );
11056
+ if (restored !== "none") {
11057
+ process.stderr.write(`[conveyor-agent] WIP snapshot restore: ${restored}
11267
11058
  `);
11268
- }
11269
11059
  }
11270
11060
  }
11271
11061
  this.mode.applyServerMode(this.fullContext?.agentMode, this.fullContext?.isAuto);
@@ -11300,6 +11090,7 @@ var SessionRunner = class _SessionRunner {
11300
11090
  `
11301
11091
  );
11302
11092
  }
11093
+ this.workspaceCommands?.notifyLoopReady();
11303
11094
  while (!this.stopped) {
11304
11095
  if (this._state !== "idle") await this.setState("idle");
11305
11096
  await this.coreLoop();
@@ -11369,6 +11160,7 @@ var SessionRunner = class _SessionRunner {
11369
11160
  this.interrupted = false;
11370
11161
  continue;
11371
11162
  }
11163
+ await this.flushWipNow("WIP: end of turn");
11372
11164
  if (!this.stopped) await this.setState("idle");
11373
11165
  } else if (this._state === "error") {
11374
11166
  await this.setState("idle");
@@ -11395,6 +11187,7 @@ var SessionRunner = class _SessionRunner {
11395
11187
  "[conveyor-agent] Completed \u2014 entering dormant idle (staying connected)\n"
11396
11188
  );
11397
11189
  }
11190
+ await this.flushWipNow("WIP: turn complete");
11398
11191
  this.pendingMessages.length = 0;
11399
11192
  if (this._state !== "idle") await this.setState("idle");
11400
11193
  const remainingMs = Math.max(0, this.dormantDeadline - Date.now());
@@ -11614,6 +11407,36 @@ var SessionRunner = class _SessionRunner {
11614
11407
  }
11615
11408
  }
11616
11409
  // ── Stop / soft-stop ───────────────────────────────────────────────
11410
+ /** Shared token-refresh closure for the git-flush call sites (periodic
11411
+ * backstop + per-turn flush). Never throws — returns undefined so the
11412
+ * caller falls back to its existing remote credentials. */
11413
+ async refreshGithubTokenForFlush() {
11414
+ try {
11415
+ const res = await this.connection.call("refreshGithubToken", {
11416
+ sessionId: this.connection.sessionId
11417
+ });
11418
+ return res.token;
11419
+ } catch {
11420
+ return void 0;
11421
+ }
11422
+ }
11423
+ /** Push uncommitted work to the conveyor-wip ref after a turn. Git-only (no
11424
+ * GCS). Primary-branch only — cheap enough to run every turn. Shares the
11425
+ * in-flight guard with the periodic backstop so a turn-end flush and a
11426
+ * timer tick never overlap. Best-effort, never throws. */
11427
+ async flushWipNow(wipMessage) {
11428
+ if (this.periodicFlushInFlight || this.stopped) return;
11429
+ this.periodicFlushInFlight = true;
11430
+ try {
11431
+ await flushPendingChanges(this.config.workspaceDir, {
11432
+ wipMessage,
11433
+ refreshToken: () => this.refreshGithubTokenForFlush()
11434
+ });
11435
+ } catch {
11436
+ } finally {
11437
+ this.periodicFlushInFlight = false;
11438
+ }
11439
+ }
11617
11440
  /** Periodic best-effort WIP commit + push during normal agent execution.
11618
11441
  * Covers ungraceful pod termination (OOMKilled, node crash/eviction) where
11619
11442
  * the preStop hook + SIGTERM flush don't get a chance to run. No-ops on a
@@ -11632,16 +11455,7 @@ var SessionRunner = class _SessionRunner {
11632
11455
  try {
11633
11456
  const result = await flushAllPendingWork(this.config.workspaceDir, {
11634
11457
  wipMessage: "WIP: periodic auto-commit",
11635
- refreshToken: async () => {
11636
- try {
11637
- const res = await this.connection.call("refreshGithubToken", {
11638
- sessionId: this.connection.sessionId
11639
- });
11640
- return res.token;
11641
- } catch {
11642
- return void 0;
11643
- }
11644
- }
11458
+ refreshToken: () => this.refreshGithubTokenForFlush()
11645
11459
  });
11646
11460
  if (result.hadWork) {
11647
11461
  process.stderr.write(
@@ -11649,7 +11463,6 @@ var SessionRunner = class _SessionRunner {
11649
11463
  `
11650
11464
  );
11651
11465
  }
11652
- await this.uploadGcsSnapshotIfConfigured();
11653
11466
  } catch {
11654
11467
  } finally {
11655
11468
  this.periodicFlushInFlight = false;
@@ -11675,22 +11488,6 @@ var SessionRunner = class _SessionRunner {
11675
11488
  });
11676
11489
  }
11677
11490
  }
11678
- /** PUT the WorkPreservation snapshot tar to the bundle's capability URL
11679
- * when this pod is v3 (CONVEYOR_SNAPSHOT_UPLOAD_URL set). Never throws. */
11680
- async uploadGcsSnapshotIfConfigured() {
11681
- const uploadUrl = process.env.CONVEYOR_SNAPSHOT_UPLOAD_URL;
11682
- if (!uploadUrl) return;
11683
- try {
11684
- const gcs = await uploadSnapshotToGcs(this.config.workspaceDir, uploadUrl);
11685
- if (gcs.uploaded) {
11686
- process.stderr.write(
11687
- `[conveyor-agent] WorkPreservation GCS snapshot: files=${gcs.fileCount} bytes=${gcs.sizeBytes}
11688
- `
11689
- );
11690
- }
11691
- } catch {
11692
- }
11693
- }
11694
11491
  /** Best-effort WIP commit + push on shutdown so in-flight work isn't lost
11695
11492
  * when a claudespace pod is killed. Must be called BEFORE stop() so the
11696
11493
  * connection is still alive for token refresh. Never throws. */
@@ -11715,7 +11512,6 @@ var SessionRunner = class _SessionRunner {
11715
11512
  `
11716
11513
  );
11717
11514
  }
11718
- await this.uploadGcsSnapshotIfConfigured();
11719
11515
  } catch (err) {
11720
11516
  const msg = err instanceof Error ? err.message : String(err);
11721
11517
  process.stderr.write(`[conveyor-agent] Shutdown git flush failed: ${msg}
@@ -11863,37 +11659,6 @@ var SessionRunner = class _SessionRunner {
11863
11659
  this.connection.onPullBranch(({ branch }) => {
11864
11660
  void handlePullBranch(this.config.workspaceDir, branch);
11865
11661
  });
11866
- this.connection.onFinalizeSnapshot(() => void this.finalizeSnapshotNow());
11867
- }
11868
- /** Eager finalize snapshot triggered by the reconciler's sleep signal
11869
- * (session:finalizeSnapshot). Runs a full workspace capture and uploads it
11870
- * NOW so the sleep confirms on this snapshot instead of waiting for the
11871
- * ~2min periodic flush. Sidecar DB state is intentionally NOT captured — it
11872
- * is no longer durable across sleep/wake (#2623); only the agent's
11873
- * /workspace rides the snapshot. Best-effort: on failure the
11874
- * periodic/shutdown path stays the fallback. No-op on non-v3 pods. */
11875
- async finalizeSnapshotNow() {
11876
- const uploadUrl = process.env.CONVEYOR_SNAPSHOT_UPLOAD_URL;
11877
- if (!uploadUrl || this.stopped) return;
11878
- try {
11879
- await finalizeForSleep({
11880
- cwd: this.config.workspaceDir,
11881
- branch: this.fullContext?.githubBranch ?? "",
11882
- snapshotUploadUrl: uploadUrl,
11883
- refreshToken: async () => {
11884
- try {
11885
- const res = await this.connection.call("refreshGithubToken", {
11886
- sessionId: this.connection.sessionId
11887
- });
11888
- return res.token;
11889
- } catch {
11890
- return void 0;
11891
- }
11892
- }
11893
- });
11894
- process.stderr.write("[conveyor-agent] Finalize snapshot uploaded on sleep signal\n");
11895
- } catch {
11896
- }
11897
11662
  }
11898
11663
  /** Proactively refresh the GitHub token before the 1-hour expiry. */
11899
11664
  async refreshGithubToken() {
@@ -11989,12 +11754,12 @@ var SessionRunner = class _SessionRunner {
11989
11754
  };
11990
11755
 
11991
11756
  // src/setup/config.ts
11992
- import { join as join10 } from "path";
11757
+ import { join as join9 } from "path";
11993
11758
  var DEVCONTAINER_PATH = ".devcontainer/conveyor/devcontainer.json";
11994
11759
  var DEVCONTAINER_PORT_DENY_LIST = /* @__PURE__ */ new Set([5432, 6379, 9200]);
11995
11760
  async function loadForwardPorts(workspaceDir) {
11996
11761
  try {
11997
- const raw = await readWorkspaceFile(join10(workspaceDir, DEVCONTAINER_PATH));
11762
+ const raw = await readWorkspaceFile(join9(workspaceDir, DEVCONTAINER_PATH));
11998
11763
  const parsed = JSON.parse(raw);
11999
11764
  const ports = (parsed.forwardPorts ?? []).filter(
12000
11765
  (p) => typeof p === "number" && !DEVCONTAINER_PORT_DENY_LIST.has(p)
@@ -12082,16 +11847,10 @@ export {
12082
11847
  sampleKeyUsage,
12083
11848
  awaitGitReady,
12084
11849
  PortDiscovery,
12085
- uploadSnapshotToGcs,
12086
- captureSnapshot,
12087
- startPeriodic,
12088
- stop,
12089
- finalizeForSleep,
12090
- restoreOnBoot,
12091
11850
  SessionRunner,
12092
11851
  loadForwardPorts,
12093
11852
  buildSessionPreviewPorts,
12094
11853
  loadConveyorConfig,
12095
11854
  unshallowRepo
12096
11855
  };
12097
- //# sourceMappingURL=chunk-TB5SQIGX.js.map
11856
+ //# sourceMappingURL=chunk-J4UH5TOO.js.map