@rallycry/conveyor-agent 9.0.0 → 9.0.2

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 {
@@ -1131,6 +1132,105 @@ async function pushToOrigin(cwd, refreshToken, skipVerify = false) {
1131
1132
  return false;
1132
1133
  }
1133
1134
  }
1135
+ function branchBackupRef(branch) {
1136
+ return `conveyor-wip/branches/${branch}`;
1137
+ }
1138
+ function listWorktrees(cwd) {
1139
+ try {
1140
+ const out = execSync("git worktree list --porcelain", {
1141
+ cwd,
1142
+ stdio: ["ignore", "pipe", "ignore"]
1143
+ }).toString();
1144
+ const result = [];
1145
+ for (const entry of out.split("\n\n")) {
1146
+ const lines = entry.trim().split("\n");
1147
+ const wl = lines.find((l) => l.startsWith("worktree "));
1148
+ if (!wl) continue;
1149
+ const bl = lines.find((l) => l.startsWith("branch "));
1150
+ result.push({
1151
+ path: wl.slice("worktree ".length),
1152
+ branch: bl ? bl.slice("branch refs/heads/".length) : null
1153
+ });
1154
+ }
1155
+ return result;
1156
+ } catch {
1157
+ return [];
1158
+ }
1159
+ }
1160
+ function listLocalBranches(cwd) {
1161
+ try {
1162
+ return execSync("git for-each-ref --format='%(refname:short)' refs/heads/", {
1163
+ cwd,
1164
+ stdio: ["ignore", "pipe", "ignore"]
1165
+ }).toString().split("\n").map((s) => s.trim()).filter(Boolean);
1166
+ } catch {
1167
+ return [];
1168
+ }
1169
+ }
1170
+ function branchUnpushedCount(cwd, branch) {
1171
+ try {
1172
+ const n = execSync(`git rev-list --count ${JSON.stringify(branch)} --not --remotes=origin`, {
1173
+ cwd,
1174
+ stdio: ["ignore", "pipe", "ignore"]
1175
+ }).toString().trim();
1176
+ return Number.parseInt(n, 10) || 0;
1177
+ } catch {
1178
+ return 0;
1179
+ }
1180
+ }
1181
+ function samePath(a, b) {
1182
+ try {
1183
+ return realpathSync(a) === realpathSync(b);
1184
+ } catch {
1185
+ return a === b;
1186
+ }
1187
+ }
1188
+ async function flushAllPendingWork(cwd, opts) {
1189
+ try {
1190
+ const primary = await flushPendingChanges(cwd, opts);
1191
+ await refreshRemoteToken(cwd, opts?.refreshToken);
1192
+ const currentBranch = getCurrentBranch(cwd);
1193
+ const worktreesSnapshotted = snapshotOtherWorktrees(cwd, opts?.wipMessage);
1194
+ const branchesBackedUp = backupOtherBranches(cwd, currentBranch);
1195
+ return {
1196
+ hadWork: primary.hadWork || worktreesSnapshotted > 0 || branchesBackedUp > 0,
1197
+ branchesBackedUp,
1198
+ worktreesSnapshotted
1199
+ };
1200
+ } catch {
1201
+ return { hadWork: false, branchesBackedUp: 0, worktreesSnapshotted: 0 };
1202
+ }
1203
+ }
1204
+ function snapshotOtherWorktrees(cwd, wipMessage) {
1205
+ let count = 0;
1206
+ for (const wt of listWorktrees(cwd)) {
1207
+ if (samePath(wt.path, cwd) || !wt.branch) continue;
1208
+ try {
1209
+ if (!hasUncommittedChanges(wt.path)) continue;
1210
+ const sha = createWipSnapshot(wt.path, wipMessage ?? "WIP: conveyor-agent snapshot");
1211
+ if (sha && tryPushRefspec(wt.path, `${sha}:refs/heads/${wipRefForBranch(wt.branch)}`, true)) {
1212
+ wipRefPushed.add(wt.path);
1213
+ count++;
1214
+ }
1215
+ } catch {
1216
+ }
1217
+ }
1218
+ return count;
1219
+ }
1220
+ function backupOtherBranches(cwd, currentBranch) {
1221
+ let count = 0;
1222
+ for (const branch of listLocalBranches(cwd)) {
1223
+ if (branch === currentBranch || branch.startsWith("conveyor-wip/")) continue;
1224
+ try {
1225
+ if (branchUnpushedCount(cwd, branch) === 0) continue;
1226
+ if (tryPushRefspec(cwd, `refs/heads/${branch}:refs/heads/${branchBackupRef(branch)}`, true)) {
1227
+ count++;
1228
+ }
1229
+ } catch {
1230
+ }
1231
+ }
1232
+ return count;
1233
+ }
1134
1234
 
1135
1235
  // src/runner/plan-sync.ts
1136
1236
  import { readdirSync, statSync, readFileSync } from "fs";
@@ -2982,6 +3082,8 @@ function buildPromptBytes(text) {
2982
3082
  var SUBMIT_SETTLE_MS = 300;
2983
3083
  var SUBMIT_NUDGE_INTERVAL_MS = 2e3;
2984
3084
  var SUBMIT_NUDGE_MAX_PRESSES = 5;
3085
+ var SUBMIT_NUDGE_SLOW_INTERVAL_MS = 5e3;
3086
+ var SUBMIT_NUDGE_WINDOW_MS = 9e4;
2985
3087
  function sleep2(ms) {
2986
3088
  return new Promise((resolve) => {
2987
3089
  setTimeout(resolve, ms);
@@ -3230,18 +3332,28 @@ var PtySession = class {
3230
3332
  * Deliberate trade-off (same as the plan-dialog auto-accept below): a press
3231
3333
  * can accept the default of an unexpected startup dialog — in a headless
3232
3334
  * pod that unblocks the run rather than parking it forever.
3335
+ *
3336
+ * Two phases: SUBMIT_NUDGE_MAX_PRESSES fast presses for the swallowed-Enter
3337
+ * race, then a slow cadence out to SUBMIT_NUDGE_WINDOW_MS for startup
3338
+ * dialogs that render late on cold pods (the folder-trust dialog's default
3339
+ * is the accept option, so one late Enter un-parks the run).
3233
3340
  */
3234
3341
  armSubmitNudge() {
3235
3342
  if (this.submitNudgeTimer) return;
3236
3343
  this.pendingSubmitNudge = true;
3344
+ const startedAt = Date.now();
3237
3345
  let presses = 0;
3238
3346
  const press = () => {
3239
- if (this._toreDown || !this.pendingSubmitNudge || presses >= SUBMIT_NUDGE_MAX_PRESSES) {
3347
+ if (this._toreDown || !this.pendingSubmitNudge || Date.now() - startedAt >= SUBMIT_NUDGE_WINDOW_MS) {
3240
3348
  this.disarmSubmitNudge();
3241
3349
  return;
3242
3350
  }
3243
3351
  presses++;
3244
3352
  this.writeStdin("\r");
3353
+ if (presses === SUBMIT_NUDGE_MAX_PRESSES && this.submitNudgeTimer) {
3354
+ clearInterval(this.submitNudgeTimer);
3355
+ this.submitNudgeTimer = setInterval(press, SUBMIT_NUDGE_SLOW_INTERVAL_MS);
3356
+ }
3245
3357
  };
3246
3358
  this.submitNudgeTimer = setInterval(press, SUBMIT_NUDGE_INTERVAL_MS);
3247
3359
  }
@@ -3467,6 +3579,18 @@ async function ensureClaudeOnboarding(env = process.env, trustCwd) {
3467
3579
  const contents = planClaudeJsonSeed(await readRaw(path), trustCwd);
3468
3580
  if (contents === null) return;
3469
3581
  await writeFile3(path, contents, "utf8");
3582
+ const verify = await readRaw(path);
3583
+ if (verify === contents) {
3584
+ process.stderr.write(
3585
+ `[conveyor-agent] claude onboarding seeded${trustCwd ? ` (trust: ${trustCwd})` : ""}
3586
+ `
3587
+ );
3588
+ } else {
3589
+ process.stderr.write(
3590
+ `[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
3591
+ `
3592
+ );
3593
+ }
3470
3594
  } catch (err) {
3471
3595
  const message = err instanceof Error ? err.message : String(err);
3472
3596
  process.stderr.write(`[conveyor-agent] claude onboarding seed failed: ${message}
@@ -8217,7 +8341,7 @@ var SessionRunner = class _SessionRunner {
8217
8341
  if (this.periodicFlushInFlight || this.stopped) return;
8218
8342
  this.periodicFlushInFlight = true;
8219
8343
  try {
8220
- const result = await flushPendingChanges(this.config.workspaceDir, {
8344
+ const result = await flushAllPendingWork(this.config.workspaceDir, {
8221
8345
  wipMessage: "WIP: periodic auto-commit",
8222
8346
  refreshToken: async () => {
8223
8347
  try {
@@ -8232,7 +8356,7 @@ var SessionRunner = class _SessionRunner {
8232
8356
  });
8233
8357
  if (result.hadWork) {
8234
8358
  process.stderr.write(
8235
- `[conveyor-agent] Periodic git flush: committed=${result.committed} pushed=${result.pushed}
8359
+ `[conveyor-agent] Periodic git flush: branchesBackedUp=${result.branchesBackedUp} worktreesSnapshotted=${result.worktreesSnapshotted}
8236
8360
  `
8237
8361
  );
8238
8362
  }
@@ -8246,7 +8370,7 @@ var SessionRunner = class _SessionRunner {
8246
8370
  * connection is still alive for token refresh. Never throws. */
8247
8371
  async flushGitOnShutdown() {
8248
8372
  try {
8249
- const result = await flushPendingChanges(this.config.workspaceDir, {
8373
+ const result = await flushAllPendingWork(this.config.workspaceDir, {
8250
8374
  wipMessage: "WIP: auto-commit on conveyor-agent shutdown",
8251
8375
  refreshToken: async () => {
8252
8376
  try {
@@ -8261,7 +8385,7 @@ var SessionRunner = class _SessionRunner {
8261
8385
  });
8262
8386
  if (result.hadWork) {
8263
8387
  process.stderr.write(
8264
- `[conveyor-agent] Shutdown git flush: committed=${result.committed} pushed=${result.pushed}
8388
+ `[conveyor-agent] Shutdown git flush: branchesBackedUp=${result.branchesBackedUp} worktreesSnapshotted=${result.worktreesSnapshotted}
8265
8389
  `
8266
8390
  );
8267
8391
  }
@@ -8758,4 +8882,4 @@ export {
8758
8882
  runStartCommand,
8759
8883
  unshallowRepo
8760
8884
  };
8761
- //# sourceMappingURL=chunk-JH7GUOGW.js.map
8885
+ //# sourceMappingURL=chunk-5W6MHROF.js.map