@rallycry/conveyor-agent 5.12.0 → 5.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -230,6 +230,18 @@ var ConveyorConnection = class _ConveyorConnection {
230
230
  resolve2();
231
231
  }
232
232
  });
233
+ this.socket.on("disconnect", (reason) => {
234
+ process.stderr.write(`[conveyor] Socket disconnected: ${reason}
235
+ `);
236
+ });
237
+ this.socket.io.on("reconnect", (attempt) => {
238
+ process.stderr.write(`[conveyor] Reconnected after ${attempt} attempt(s)
239
+ `);
240
+ });
241
+ this.socket.io.on("reconnect_error", (err) => {
242
+ process.stderr.write(`[conveyor] Reconnection error: ${err.message}
243
+ `);
244
+ });
233
245
  this.socket.io.on("reconnect_attempt", () => {
234
246
  attempts++;
235
247
  if (!settled && attempts >= maxInitialAttempts) {
@@ -557,6 +569,18 @@ var ProjectConnection = class {
557
569
  resolve2();
558
570
  }
559
571
  });
572
+ this.socket.on("disconnect", (reason) => {
573
+ process.stderr.write(`[conveyor] Project socket disconnected: ${reason}
574
+ `);
575
+ });
576
+ this.socket.io.on("reconnect", (attempt) => {
577
+ process.stderr.write(`[conveyor] Project socket reconnected after ${attempt} attempt(s)
578
+ `);
579
+ });
580
+ this.socket.io.on("reconnect_error", (err) => {
581
+ process.stderr.write(`[conveyor] Project socket reconnection error: ${err.message}
582
+ `);
583
+ });
560
584
  this.socket.io.on("reconnect_attempt", () => {
561
585
  attempts++;
562
586
  if (!settled && attempts >= maxInitialAttempts) {
@@ -733,9 +757,32 @@ var ProjectConnection = class {
733
757
  };
734
758
 
735
759
  // src/runner/worktree.ts
736
- import { execSync as execSync2 } from "child_process";
760
+ import { execSync as execSync3 } from "child_process";
737
761
  import { existsSync } from "fs";
738
762
  import { join } from "path";
763
+
764
+ // src/runner/git-utils.ts
765
+ import { execSync as execSync2 } from "child_process";
766
+ function hasUncommittedChanges(cwd) {
767
+ const status = execSync2("git status --porcelain", {
768
+ cwd,
769
+ stdio: ["ignore", "pipe", "ignore"]
770
+ }).toString().trim();
771
+ return status.length > 0;
772
+ }
773
+ function getCurrentBranch(cwd) {
774
+ try {
775
+ const branch = execSync2("git branch --show-current", {
776
+ cwd,
777
+ stdio: ["ignore", "pipe", "ignore"]
778
+ }).toString().trim();
779
+ return branch || null;
780
+ } catch {
781
+ return null;
782
+ }
783
+ }
784
+
785
+ // src/runner/worktree.ts
739
786
  var WORKTREE_DIR = ".worktrees";
740
787
  function ensureWorktree(projectDir, taskId, branch) {
741
788
  if (projectDir.includes(`/${WORKTREE_DIR}/`)) {
@@ -744,8 +791,11 @@ function ensureWorktree(projectDir, taskId, branch) {
744
791
  const worktreePath = join(projectDir, WORKTREE_DIR, taskId);
745
792
  if (existsSync(worktreePath)) {
746
793
  if (branch) {
794
+ if (hasUncommittedChanges(worktreePath)) {
795
+ return worktreePath;
796
+ }
747
797
  try {
748
- execSync2(`git checkout --detach origin/${branch}`, {
798
+ execSync3(`git checkout --detach origin/${branch}`, {
749
799
  cwd: worktreePath,
750
800
  stdio: "ignore"
751
801
  });
@@ -755,17 +805,39 @@ function ensureWorktree(projectDir, taskId, branch) {
755
805
  return worktreePath;
756
806
  }
757
807
  const ref = branch ? `origin/${branch}` : "HEAD";
758
- execSync2(`git worktree add --detach "${worktreePath}" ${ref}`, {
808
+ execSync3(`git worktree add --detach "${worktreePath}" ${ref}`, {
759
809
  cwd: projectDir,
760
810
  stdio: "ignore"
761
811
  });
762
812
  return worktreePath;
763
813
  }
814
+ function detachWorktreeBranch(projectDir, branch) {
815
+ try {
816
+ const output = execSync3("git worktree list --porcelain", {
817
+ cwd: projectDir,
818
+ encoding: "utf-8"
819
+ });
820
+ const entries = output.split("\n\n");
821
+ for (const entry of entries) {
822
+ const lines = entry.trim().split("\n");
823
+ const worktreeLine = lines.find((l) => l.startsWith("worktree "));
824
+ const branchLine = lines.find((l) => l.startsWith("branch "));
825
+ if (!worktreeLine || branchLine !== `branch refs/heads/${branch}`) continue;
826
+ const worktreePath = worktreeLine.replace("worktree ", "");
827
+ if (!worktreePath.includes(`/${WORKTREE_DIR}/`)) continue;
828
+ try {
829
+ execSync3("git checkout --detach", { cwd: worktreePath, stdio: "ignore" });
830
+ } catch {
831
+ }
832
+ }
833
+ } catch {
834
+ }
835
+ }
764
836
  function removeWorktree(projectDir, taskId) {
765
837
  const worktreePath = join(projectDir, WORKTREE_DIR, taskId);
766
838
  if (!existsSync(worktreePath)) return;
767
839
  try {
768
- execSync2(`git worktree remove "${worktreePath}" --force`, {
840
+ execSync3(`git worktree remove "${worktreePath}" --force`, {
769
841
  cwd: projectDir,
770
842
  stdio: "ignore"
771
843
  });
@@ -801,10 +873,10 @@ function loadConveyorConfig(_workspaceDir) {
801
873
  }
802
874
 
803
875
  // src/setup/codespace.ts
804
- import { execSync as execSync3 } from "child_process";
876
+ import { execSync as execSync4 } from "child_process";
805
877
  function unshallowRepo(workspaceDir) {
806
878
  try {
807
- execSync3("git fetch --unshallow", {
879
+ execSync4("git fetch --unshallow", {
808
880
  cwd: workspaceDir,
809
881
  stdio: "ignore",
810
882
  timeout: 6e4
@@ -842,7 +914,7 @@ function errorMeta(error) {
842
914
 
843
915
  // src/runner/agent-runner.ts
844
916
  import { randomUUID as randomUUID2 } from "crypto";
845
- import { execSync as execSync4 } from "child_process";
917
+ import { execSync as execSync5 } from "child_process";
846
918
 
847
919
  // src/execution/event-handlers.ts
848
920
  function safeVoid(promise, context) {
@@ -3476,6 +3548,47 @@ async function executeSetupConfig(config, runnerConfig, connection, setupLog) {
3476
3548
  async function checkoutTaskBranch(runnerConfig, connection, callbacks, setupLog) {
3477
3549
  const taskBranch = process.env.CONVEYOR_TASK_BRANCH;
3478
3550
  if (!taskBranch) return true;
3551
+ const currentBranch = getCurrentBranch(runnerConfig.workspaceDir);
3552
+ if (currentBranch === taskBranch) {
3553
+ pushSetupLog(setupLog, `[conveyor] Already on ${taskBranch}, skipping checkout`);
3554
+ connection.sendEvent({
3555
+ type: "setup_output",
3556
+ stream: "stdout",
3557
+ data: `Already on branch ${taskBranch}, skipping checkout
3558
+ `
3559
+ });
3560
+ try {
3561
+ await runSetupCommand(
3562
+ `git fetch origin ${taskBranch}`,
3563
+ runnerConfig.workspaceDir,
3564
+ (stream, data) => {
3565
+ connection.sendEvent({ type: "setup_output", stream, data });
3566
+ }
3567
+ );
3568
+ } catch {
3569
+ }
3570
+ return true;
3571
+ }
3572
+ let didStash = false;
3573
+ if (hasUncommittedChanges(runnerConfig.workspaceDir)) {
3574
+ pushSetupLog(setupLog, `[conveyor] Uncommitted changes detected, stashing before checkout`);
3575
+ connection.sendEvent({
3576
+ type: "setup_output",
3577
+ stream: "stdout",
3578
+ data: "Uncommitted changes detected \u2014 stashing before branch switch\n"
3579
+ });
3580
+ try {
3581
+ await runSetupCommand(
3582
+ `git stash push -m "conveyor-auto-stash"`,
3583
+ runnerConfig.workspaceDir,
3584
+ (stream, data) => {
3585
+ connection.sendEvent({ type: "setup_output", stream, data });
3586
+ }
3587
+ );
3588
+ didStash = true;
3589
+ } catch {
3590
+ }
3591
+ }
3479
3592
  pushSetupLog(setupLog, `[conveyor] Switching to task branch ${taskBranch}...`);
3480
3593
  connection.sendEvent({
3481
3594
  type: "setup_output",
@@ -3495,6 +3608,19 @@ async function checkoutTaskBranch(runnerConfig, connection, callbacks, setupLog)
3495
3608
  }
3496
3609
  );
3497
3610
  pushSetupLog(setupLog, `[conveyor] Switched to ${taskBranch}`);
3611
+ if (didStash) {
3612
+ try {
3613
+ await runSetupCommand("git stash pop", runnerConfig.workspaceDir, (stream, data) => {
3614
+ connection.sendEvent({ type: "setup_output", stream, data });
3615
+ });
3616
+ pushSetupLog(setupLog, `[conveyor] Restored stashed changes`);
3617
+ } catch {
3618
+ pushSetupLog(
3619
+ setupLog,
3620
+ `[conveyor] Warning: stash pop had conflicts \u2014 agent may need to resolve`
3621
+ );
3622
+ }
3623
+ }
3498
3624
  return true;
3499
3625
  } catch (error) {
3500
3626
  const message = `Failed to checkout ${taskBranch}: ${error instanceof Error ? error.message : "unknown error"}`;
@@ -3839,12 +3965,22 @@ var AgentRunner = class {
3839
3965
  }
3840
3966
  checkoutWorktreeBranch() {
3841
3967
  if (!this.worktreeActive || !this.taskContext?.githubBranch) return;
3968
+ const branch = this.taskContext.githubBranch;
3969
+ const cwd = this.config.workspaceDir;
3970
+ if (getCurrentBranch(cwd) === branch) return;
3842
3971
  try {
3843
- const branch = this.taskContext.githubBranch;
3844
- execSync4(`git fetch origin ${branch} && git checkout ${branch}`, {
3845
- cwd: this.config.workspaceDir,
3846
- stdio: "ignore"
3847
- });
3972
+ let didStash = false;
3973
+ if (hasUncommittedChanges(cwd)) {
3974
+ execSync5(`git stash push -m "conveyor-auto-stash"`, { cwd, stdio: "ignore" });
3975
+ didStash = true;
3976
+ }
3977
+ execSync5(`git fetch origin ${branch} && git checkout ${branch}`, { cwd, stdio: "ignore" });
3978
+ if (didStash) {
3979
+ try {
3980
+ execSync5("git stash pop", { cwd, stdio: "ignore" });
3981
+ } catch {
3982
+ }
3983
+ }
3848
3984
  } catch {
3849
3985
  }
3850
3986
  }
@@ -4132,12 +4268,12 @@ var AgentRunner = class {
4132
4268
 
4133
4269
  // src/runner/project-runner.ts
4134
4270
  import { fork } from "child_process";
4135
- import { execSync as execSync6 } from "child_process";
4271
+ import { execSync as execSync7 } from "child_process";
4136
4272
  import * as path from "path";
4137
4273
  import { fileURLToPath } from "url";
4138
4274
 
4139
4275
  // src/runner/commit-watcher.ts
4140
- import { execSync as execSync5 } from "child_process";
4276
+ import { execSync as execSync6 } from "child_process";
4141
4277
  var logger3 = createServiceLogger("CommitWatcher");
4142
4278
  var CommitWatcher = class {
4143
4279
  constructor(config, callbacks) {
@@ -4169,7 +4305,7 @@ var CommitWatcher = class {
4169
4305
  this.isSyncing = false;
4170
4306
  }
4171
4307
  getLocalHeadSha() {
4172
- return execSync5("git rev-parse HEAD", {
4308
+ return execSync6("git rev-parse HEAD", {
4173
4309
  cwd: this.config.projectDir,
4174
4310
  stdio: ["ignore", "pipe", "ignore"]
4175
4311
  }).toString().trim();
@@ -4177,12 +4313,12 @@ var CommitWatcher = class {
4177
4313
  poll() {
4178
4314
  if (!this.branch || this.isSyncing) return;
4179
4315
  try {
4180
- execSync5(`git fetch origin ${this.branch} --quiet`, {
4316
+ execSync6(`git fetch origin ${this.branch} --quiet`, {
4181
4317
  cwd: this.config.projectDir,
4182
4318
  stdio: "ignore",
4183
4319
  timeout: 3e4
4184
4320
  });
4185
- const remoteSha = execSync5(`git rev-parse origin/${this.branch}`, {
4321
+ const remoteSha = execSync6(`git rev-parse origin/${this.branch}`, {
4186
4322
  cwd: this.config.projectDir,
4187
4323
  stdio: ["ignore", "pipe", "ignore"]
4188
4324
  }).toString().trim();
@@ -4203,12 +4339,12 @@ var CommitWatcher = class {
4203
4339
  let latestMessage = "";
4204
4340
  let latestAuthor = "";
4205
4341
  try {
4206
- const countOutput = execSync5(`git rev-list --count ${previousSha}..origin/${this.branch}`, {
4342
+ const countOutput = execSync6(`git rev-list --count ${previousSha}..origin/${this.branch}`, {
4207
4343
  cwd: this.config.projectDir,
4208
4344
  stdio: ["ignore", "pipe", "ignore"]
4209
4345
  }).toString().trim();
4210
4346
  commitCount = parseInt(countOutput, 10) || 1;
4211
- const logOutput = execSync5(`git log -1 --format="%s|||%an" origin/${this.branch}`, {
4347
+ const logOutput = execSync6(`git log -1 --format="%s|||%an" origin/${this.branch}`, {
4212
4348
  cwd: this.config.projectDir,
4213
4349
  stdio: ["ignore", "pipe", "ignore"]
4214
4350
  }).toString().trim();
@@ -5008,13 +5144,20 @@ function setupWorkDir(projectDir, assignment) {
5008
5144
  workDir = projectDir;
5009
5145
  }
5010
5146
  if (branch && branch !== devBranch) {
5011
- try {
5012
- execSync6(`git checkout ${branch}`, { cwd: workDir, stdio: "ignore" });
5013
- } catch {
5147
+ if (hasUncommittedChanges(workDir)) {
5148
+ logger6.warn("Uncommitted changes in work dir, skipping checkout", {
5149
+ taskId: shortId,
5150
+ branch
5151
+ });
5152
+ } else {
5014
5153
  try {
5015
- execSync6(`git checkout -b ${branch}`, { cwd: workDir, stdio: "ignore" });
5154
+ execSync7(`git checkout ${branch}`, { cwd: workDir, stdio: "ignore" });
5016
5155
  } catch {
5017
- logger6.warn("Could not checkout branch", { taskId: shortId, branch });
5156
+ try {
5157
+ execSync7(`git checkout -b ${branch}`, { cwd: workDir, stdio: "ignore" });
5158
+ } catch {
5159
+ logger6.warn("Could not checkout branch", { taskId: shortId, branch });
5160
+ }
5018
5161
  }
5019
5162
  }
5020
5163
  }
@@ -5119,9 +5262,20 @@ var ProjectRunner = class {
5119
5262
  checkoutWorkspaceBranch() {
5120
5263
  const workspaceBranch = process.env.CONVEYOR_WORKSPACE_BRANCH;
5121
5264
  if (!workspaceBranch) return;
5265
+ const currentBranch = this.getCurrentBranch();
5266
+ if (currentBranch === workspaceBranch) {
5267
+ logger6.info("Already on workspace branch", { workspaceBranch });
5268
+ return;
5269
+ }
5270
+ if (hasUncommittedChanges(this.projectDir)) {
5271
+ logger6.warn("Uncommitted changes detected, skipping workspace branch checkout", {
5272
+ workspaceBranch
5273
+ });
5274
+ return;
5275
+ }
5122
5276
  try {
5123
- execSync6(`git fetch origin ${workspaceBranch}`, { cwd: this.projectDir, stdio: "pipe" });
5124
- execSync6(`git checkout ${workspaceBranch}`, { cwd: this.projectDir, stdio: "pipe" });
5277
+ execSync7(`git fetch origin ${workspaceBranch}`, { cwd: this.projectDir, stdio: "pipe" });
5278
+ execSync7(`git checkout ${workspaceBranch}`, { cwd: this.projectDir, stdio: "pipe" });
5125
5279
  logger6.info("Checked out workspace branch", { workspaceBranch });
5126
5280
  } catch (err) {
5127
5281
  logger6.warn("Failed to checkout workspace branch, continuing on current branch", {
@@ -5209,39 +5363,20 @@ var ProjectRunner = class {
5209
5363
  this.executeStartCommand();
5210
5364
  }
5211
5365
  getEnvironmentStatus() {
5212
- let currentBranch = "unknown";
5213
- try {
5214
- currentBranch = execSync6("git branch --show-current", {
5215
- cwd: this.projectDir,
5216
- stdio: ["ignore", "pipe", "ignore"]
5217
- }).toString().trim();
5218
- } catch {
5219
- }
5220
5366
  return {
5221
5367
  setupComplete: this.setupComplete,
5222
5368
  startCommandRunning: this.startCommandRunning,
5223
- currentBranch,
5369
+ currentBranch: this.getCurrentBranch() ?? "unknown",
5224
5370
  previewPort: Number(process.env.CONVEYOR_PREVIEW_PORT) || null
5225
5371
  };
5226
5372
  }
5227
5373
  getCurrentBranch() {
5228
- try {
5229
- return execSync6("git branch --show-current", {
5230
- cwd: this.projectDir,
5231
- stdio: ["ignore", "pipe", "ignore"]
5232
- }).toString().trim() || null;
5233
- } catch {
5234
- return null;
5235
- }
5374
+ return getCurrentBranch(this.projectDir);
5236
5375
  }
5237
5376
  // oxlint-disable-next-line max-lines-per-function, complexity -- sequential sync steps with per-step error handling
5238
5377
  async smartSync(previousSha, newSha, branch) {
5239
5378
  const stepsRun = [];
5240
- const status = execSync6("git status --porcelain", {
5241
- cwd: this.projectDir,
5242
- stdio: ["ignore", "pipe", "ignore"]
5243
- }).toString().trim();
5244
- if (status) {
5379
+ if (hasUncommittedChanges(this.projectDir)) {
5245
5380
  this.connection.emitEvent({
5246
5381
  type: "commit_watch_warning",
5247
5382
  message: "Working tree has uncommitted changes. Auto-pull skipped."
@@ -5251,7 +5386,7 @@ var ProjectRunner = class {
5251
5386
  await this.killStartCommand();
5252
5387
  this.connection.emitEnvSwitchProgress({ step: "pull", status: "running" });
5253
5388
  try {
5254
- execSync6(`git pull origin ${branch}`, {
5389
+ execSync7(`git pull origin ${branch}`, {
5255
5390
  cwd: this.projectDir,
5256
5391
  stdio: "pipe",
5257
5392
  timeout: 6e4
@@ -5267,7 +5402,7 @@ var ProjectRunner = class {
5267
5402
  }
5268
5403
  let changedFiles = [];
5269
5404
  try {
5270
- changedFiles = execSync6(`git diff --name-only ${previousSha}..${newSha}`, {
5405
+ changedFiles = execSync7(`git diff --name-only ${previousSha}..${newSha}`, {
5271
5406
  cwd: this.projectDir,
5272
5407
  stdio: ["ignore", "pipe", "ignore"]
5273
5408
  }).toString().trim().split("\n").filter(Boolean);
@@ -5297,7 +5432,7 @@ var ProjectRunner = class {
5297
5432
  if (needsInstall) {
5298
5433
  this.connection.emitEnvSwitchProgress({ step: "install", status: "running" });
5299
5434
  try {
5300
- execSync6("bun install", { cwd: this.projectDir, timeout: 12e4, stdio: "pipe" });
5435
+ execSync7("bun install", { cwd: this.projectDir, timeout: 12e4, stdio: "pipe" });
5301
5436
  stepsRun.push("install");
5302
5437
  this.connection.emitEnvSwitchProgress({ step: "install", status: "success" });
5303
5438
  } catch (err) {
@@ -5309,12 +5444,12 @@ var ProjectRunner = class {
5309
5444
  if (needsPrisma) {
5310
5445
  this.connection.emitEnvSwitchProgress({ step: "prisma", status: "running" });
5311
5446
  try {
5312
- execSync6("bunx prisma generate", {
5447
+ execSync7("bunx prisma generate", {
5313
5448
  cwd: this.projectDir,
5314
5449
  timeout: 6e4,
5315
5450
  stdio: "pipe"
5316
5451
  });
5317
- execSync6("bunx prisma db push --accept-data-loss", {
5452
+ execSync7("bunx prisma db push --accept-data-loss", {
5318
5453
  cwd: this.projectDir,
5319
5454
  timeout: 6e4,
5320
5455
  stdio: "pipe"
@@ -5337,14 +5472,15 @@ var ProjectRunner = class {
5337
5472
  try {
5338
5473
  this.connection.emitEnvSwitchProgress({ step: "fetch", status: "running" });
5339
5474
  try {
5340
- execSync6("git fetch origin", { cwd: this.projectDir, stdio: "pipe" });
5475
+ execSync7("git fetch origin", { cwd: this.projectDir, stdio: "pipe" });
5341
5476
  } catch {
5342
5477
  logger6.warn("Git fetch failed during branch switch");
5343
5478
  }
5344
5479
  this.connection.emitEnvSwitchProgress({ step: "fetch", status: "success" });
5480
+ detachWorktreeBranch(this.projectDir, branch);
5345
5481
  this.connection.emitEnvSwitchProgress({ step: "checkout", status: "running" });
5346
5482
  try {
5347
- execSync6(`git checkout ${branch}`, { cwd: this.projectDir, stdio: "pipe" });
5483
+ execSync7(`git checkout ${branch}`, { cwd: this.projectDir, stdio: "pipe" });
5348
5484
  } catch (err) {
5349
5485
  const message = err instanceof Error ? err.message : "Checkout failed";
5350
5486
  this.connection.emitEnvSwitchProgress({ step: "checkout", status: "error", message });
@@ -5352,7 +5488,7 @@ var ProjectRunner = class {
5352
5488
  return;
5353
5489
  }
5354
5490
  try {
5355
- execSync6(`git pull origin ${branch}`, { cwd: this.projectDir, stdio: "pipe" });
5491
+ execSync7(`git pull origin ${branch}`, { cwd: this.projectDir, stdio: "pipe" });
5356
5492
  } catch {
5357
5493
  logger6.warn("Git pull failed during branch switch", { branch });
5358
5494
  }
@@ -5491,7 +5627,7 @@ var ProjectRunner = class {
5491
5627
  }
5492
5628
  try {
5493
5629
  try {
5494
- execSync6("git fetch origin", { cwd: this.projectDir, stdio: "ignore" });
5630
+ execSync7("git fetch origin", { cwd: this.projectDir, stdio: "ignore" });
5495
5631
  } catch {
5496
5632
  logger6.warn("Git fetch failed", { taskId: shortId });
5497
5633
  }
@@ -5665,4 +5801,4 @@ export {
5665
5801
  ProjectRunner,
5666
5802
  FileCache
5667
5803
  };
5668
- //# sourceMappingURL=chunk-SQJJL2PU.js.map
5804
+ //# sourceMappingURL=chunk-4RFYCO2T.js.map