@rallycry/conveyor-agent 9.0.1 → 9.0.3

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.
@@ -830,6 +830,7 @@ function createServiceLogger(service) {
830
830
 
831
831
  // src/runner/git-utils.ts
832
832
  import { execSync } from "child_process";
833
+ import { realpathSync } from "fs";
833
834
  function syncWithBaseBranch(cwd, baseBranch) {
834
835
  if (!baseBranch) return true;
835
836
  try {
@@ -1057,7 +1058,18 @@ function restoreWipSnapshot(cwd, branch) {
1057
1058
  stdio: ["ignore", "pipe", "ignore"]
1058
1059
  }).toString().trim();
1059
1060
  const head = execSync("git rev-parse HEAD", { cwd, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
1060
- if (parent !== head) return "stale";
1061
+ if (parent !== head) {
1062
+ try {
1063
+ execSync(`git stash apply ${sha}`, { cwd, stdio: ["ignore", "pipe", "pipe"] });
1064
+ return "applied";
1065
+ } catch {
1066
+ try {
1067
+ execSync("git reset --merge", { cwd, stdio: "ignore" });
1068
+ } catch {
1069
+ }
1070
+ return "stale";
1071
+ }
1072
+ }
1061
1073
  execSync(`git stash apply ${sha}`, { cwd, stdio: ["ignore", "pipe", "pipe"] });
1062
1074
  return "applied";
1063
1075
  } catch {
@@ -1131,6 +1143,105 @@ async function pushToOrigin(cwd, refreshToken, skipVerify = false) {
1131
1143
  return false;
1132
1144
  }
1133
1145
  }
1146
+ function branchBackupRef(branch) {
1147
+ return `conveyor-wip/branches/${branch}`;
1148
+ }
1149
+ function listWorktrees(cwd) {
1150
+ try {
1151
+ const out = execSync("git worktree list --porcelain", {
1152
+ cwd,
1153
+ stdio: ["ignore", "pipe", "ignore"]
1154
+ }).toString();
1155
+ const result = [];
1156
+ for (const entry of out.split("\n\n")) {
1157
+ const lines = entry.trim().split("\n");
1158
+ const wl = lines.find((l) => l.startsWith("worktree "));
1159
+ if (!wl) continue;
1160
+ const bl = lines.find((l) => l.startsWith("branch "));
1161
+ result.push({
1162
+ path: wl.slice("worktree ".length),
1163
+ branch: bl ? bl.slice("branch refs/heads/".length) : null
1164
+ });
1165
+ }
1166
+ return result;
1167
+ } catch {
1168
+ return [];
1169
+ }
1170
+ }
1171
+ function listLocalBranches(cwd) {
1172
+ try {
1173
+ return execSync("git for-each-ref --format='%(refname:short)' refs/heads/", {
1174
+ cwd,
1175
+ stdio: ["ignore", "pipe", "ignore"]
1176
+ }).toString().split("\n").map((s) => s.trim()).filter(Boolean);
1177
+ } catch {
1178
+ return [];
1179
+ }
1180
+ }
1181
+ function branchUnpushedCount(cwd, branch) {
1182
+ try {
1183
+ const n = execSync(`git rev-list --count ${JSON.stringify(branch)} --not --remotes=origin`, {
1184
+ cwd,
1185
+ stdio: ["ignore", "pipe", "ignore"]
1186
+ }).toString().trim();
1187
+ return Number.parseInt(n, 10) || 0;
1188
+ } catch {
1189
+ return 0;
1190
+ }
1191
+ }
1192
+ function samePath(a, b) {
1193
+ try {
1194
+ return realpathSync(a) === realpathSync(b);
1195
+ } catch {
1196
+ return a === b;
1197
+ }
1198
+ }
1199
+ async function flushAllPendingWork(cwd, opts) {
1200
+ try {
1201
+ const primary = await flushPendingChanges(cwd, opts);
1202
+ await refreshRemoteToken(cwd, opts?.refreshToken);
1203
+ const currentBranch = getCurrentBranch(cwd);
1204
+ const worktreesSnapshotted = snapshotOtherWorktrees(cwd, opts?.wipMessage);
1205
+ const branchesBackedUp = backupOtherBranches(cwd, currentBranch);
1206
+ return {
1207
+ hadWork: primary.hadWork || worktreesSnapshotted > 0 || branchesBackedUp > 0,
1208
+ branchesBackedUp,
1209
+ worktreesSnapshotted
1210
+ };
1211
+ } catch {
1212
+ return { hadWork: false, branchesBackedUp: 0, worktreesSnapshotted: 0 };
1213
+ }
1214
+ }
1215
+ function snapshotOtherWorktrees(cwd, wipMessage) {
1216
+ let count = 0;
1217
+ for (const wt of listWorktrees(cwd)) {
1218
+ if (samePath(wt.path, cwd) || !wt.branch) continue;
1219
+ try {
1220
+ if (!hasUncommittedChanges(wt.path)) continue;
1221
+ const sha = createWipSnapshot(wt.path, wipMessage ?? "WIP: conveyor-agent snapshot");
1222
+ if (sha && tryPushRefspec(wt.path, `${sha}:refs/heads/${wipRefForBranch(wt.branch)}`, true)) {
1223
+ wipRefPushed.add(wt.path);
1224
+ count++;
1225
+ }
1226
+ } catch {
1227
+ }
1228
+ }
1229
+ return count;
1230
+ }
1231
+ function backupOtherBranches(cwd, currentBranch) {
1232
+ let count = 0;
1233
+ for (const branch of listLocalBranches(cwd)) {
1234
+ if (branch === currentBranch || branch.startsWith("conveyor-wip/")) continue;
1235
+ try {
1236
+ if (branchUnpushedCount(cwd, branch) === 0) continue;
1237
+ if (tryPushRefspec(cwd, `refs/heads/${branch}:refs/heads/${branchBackupRef(branch)}`, true)) {
1238
+ count++;
1239
+ }
1240
+ } catch {
1241
+ }
1242
+ }
1243
+ return count;
1244
+ }
1134
1245
 
1135
1246
  // src/runner/plan-sync.ts
1136
1247
  import { readdirSync, statSync, readFileSync } from "fs";
@@ -2982,6 +3093,8 @@ function buildPromptBytes(text) {
2982
3093
  var SUBMIT_SETTLE_MS = 300;
2983
3094
  var SUBMIT_NUDGE_INTERVAL_MS = 2e3;
2984
3095
  var SUBMIT_NUDGE_MAX_PRESSES = 5;
3096
+ var SUBMIT_NUDGE_SLOW_INTERVAL_MS = 5e3;
3097
+ var SUBMIT_NUDGE_WINDOW_MS = 9e4;
2985
3098
  function sleep2(ms) {
2986
3099
  return new Promise((resolve) => {
2987
3100
  setTimeout(resolve, ms);
@@ -3230,18 +3343,28 @@ var PtySession = class {
3230
3343
  * Deliberate trade-off (same as the plan-dialog auto-accept below): a press
3231
3344
  * can accept the default of an unexpected startup dialog — in a headless
3232
3345
  * pod that unblocks the run rather than parking it forever.
3346
+ *
3347
+ * Two phases: SUBMIT_NUDGE_MAX_PRESSES fast presses for the swallowed-Enter
3348
+ * race, then a slow cadence out to SUBMIT_NUDGE_WINDOW_MS for startup
3349
+ * dialogs that render late on cold pods (the folder-trust dialog's default
3350
+ * is the accept option, so one late Enter un-parks the run).
3233
3351
  */
3234
3352
  armSubmitNudge() {
3235
3353
  if (this.submitNudgeTimer) return;
3236
3354
  this.pendingSubmitNudge = true;
3355
+ const startedAt = Date.now();
3237
3356
  let presses = 0;
3238
3357
  const press = () => {
3239
- if (this._toreDown || !this.pendingSubmitNudge || presses >= SUBMIT_NUDGE_MAX_PRESSES) {
3358
+ if (this._toreDown || !this.pendingSubmitNudge || Date.now() - startedAt >= SUBMIT_NUDGE_WINDOW_MS) {
3240
3359
  this.disarmSubmitNudge();
3241
3360
  return;
3242
3361
  }
3243
3362
  presses++;
3244
3363
  this.writeStdin("\r");
3364
+ if (presses === SUBMIT_NUDGE_MAX_PRESSES && this.submitNudgeTimer) {
3365
+ clearInterval(this.submitNudgeTimer);
3366
+ this.submitNudgeTimer = setInterval(press, SUBMIT_NUDGE_SLOW_INTERVAL_MS);
3367
+ }
3245
3368
  };
3246
3369
  this.submitNudgeTimer = setInterval(press, SUBMIT_NUDGE_INTERVAL_MS);
3247
3370
  }
@@ -3467,6 +3590,18 @@ async function ensureClaudeOnboarding(env = process.env, trustCwd) {
3467
3590
  const contents = planClaudeJsonSeed(await readRaw(path), trustCwd);
3468
3591
  if (contents === null) return;
3469
3592
  await writeFile3(path, contents, "utf8");
3593
+ const verify = await readRaw(path);
3594
+ if (verify === contents) {
3595
+ process.stderr.write(
3596
+ `[conveyor-agent] claude onboarding seeded${trustCwd ? ` (trust: ${trustCwd})` : ""}
3597
+ `
3598
+ );
3599
+ } else {
3600
+ process.stderr.write(
3601
+ `[conveyor-agent] claude onboarding seed read-back MISMATCH at ${path}: wrote ${contents.length}B, read ${verify?.length ?? 0}B \u2014 CLI may see stale config and park at a startup dialog
3602
+ `
3603
+ );
3604
+ }
3470
3605
  } catch (err) {
3471
3606
  const message = err instanceof Error ? err.message : String(err);
3472
3607
  process.stderr.write(`[conveyor-agent] claude onboarding seed failed: ${message}
@@ -8217,7 +8352,7 @@ var SessionRunner = class _SessionRunner {
8217
8352
  if (this.periodicFlushInFlight || this.stopped) return;
8218
8353
  this.periodicFlushInFlight = true;
8219
8354
  try {
8220
- const result = await flushPendingChanges(this.config.workspaceDir, {
8355
+ const result = await flushAllPendingWork(this.config.workspaceDir, {
8221
8356
  wipMessage: "WIP: periodic auto-commit",
8222
8357
  refreshToken: async () => {
8223
8358
  try {
@@ -8232,7 +8367,7 @@ var SessionRunner = class _SessionRunner {
8232
8367
  });
8233
8368
  if (result.hadWork) {
8234
8369
  process.stderr.write(
8235
- `[conveyor-agent] Periodic git flush: committed=${result.committed} pushed=${result.pushed}
8370
+ `[conveyor-agent] Periodic git flush: branchesBackedUp=${result.branchesBackedUp} worktreesSnapshotted=${result.worktreesSnapshotted}
8236
8371
  `
8237
8372
  );
8238
8373
  }
@@ -8246,7 +8381,7 @@ var SessionRunner = class _SessionRunner {
8246
8381
  * connection is still alive for token refresh. Never throws. */
8247
8382
  async flushGitOnShutdown() {
8248
8383
  try {
8249
- const result = await flushPendingChanges(this.config.workspaceDir, {
8384
+ const result = await flushAllPendingWork(this.config.workspaceDir, {
8250
8385
  wipMessage: "WIP: auto-commit on conveyor-agent shutdown",
8251
8386
  refreshToken: async () => {
8252
8387
  try {
@@ -8261,7 +8396,7 @@ var SessionRunner = class _SessionRunner {
8261
8396
  });
8262
8397
  if (result.hadWork) {
8263
8398
  process.stderr.write(
8264
- `[conveyor-agent] Shutdown git flush: committed=${result.committed} pushed=${result.pushed}
8399
+ `[conveyor-agent] Shutdown git flush: branchesBackedUp=${result.branchesBackedUp} worktreesSnapshotted=${result.worktreesSnapshotted}
8265
8400
  `
8266
8401
  );
8267
8402
  }
@@ -8758,4 +8893,4 @@ export {
8758
8893
  runStartCommand,
8759
8894
  unshallowRepo
8760
8895
  };
8761
- //# sourceMappingURL=chunk-JH7GUOGW.js.map
8896
+ //# sourceMappingURL=chunk-EYIGNRRW.js.map