runcastle 1.2.2 → 1.2.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.
package/bin/runcastle.js CHANGED
@@ -914,6 +914,23 @@ var init_resolve_executable = __esm(() => {
914
914
  };
915
915
  });
916
916
 
917
+ // src/pty/kill-tree.ts
918
+ import { execFile } from "child_process";
919
+ import { promisify } from "util";
920
+ async function killProcessTree(pid) {
921
+ try {
922
+ if (process.platform === "win32") {
923
+ await execFileAsync("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true });
924
+ } else {
925
+ process.kill(-pid, "SIGTERM");
926
+ }
927
+ } catch {}
928
+ }
929
+ var execFileAsync;
930
+ var init_kill_tree = __esm(() => {
931
+ execFileAsync = promisify(execFile);
932
+ });
933
+
917
934
  // src/pty/pty-sidecar.ts
918
935
  import { spawn } from "child_process";
919
936
  import { createRequire } from "module";
@@ -1095,6 +1112,9 @@ ${why.replace(/\n/g, `\r
1095
1112
  }
1096
1113
  }, 500);
1097
1114
  },
1115
+ killTree() {
1116
+ return killProcessTree(process.platform === "win32" ? child.pid ?? pid : pid);
1117
+ },
1098
1118
  get pid() {
1099
1119
  return pid;
1100
1120
  },
@@ -1107,6 +1127,7 @@ var HOST_PATH;
1107
1127
  var init_pty_sidecar = __esm(() => {
1108
1128
  init_asset_paths();
1109
1129
  init_resolve_executable();
1130
+ init_kill_tree();
1110
1131
  HOST_PATH = resolveAsset(ASSET_ENV.ptyHost, fileURLToPath(new URL("./pty-host.cjs", import.meta.url)));
1111
1132
  });
1112
1133
 
@@ -1193,6 +1214,9 @@ function createNativePtySession(cmd, args, opts) {
1193
1214
  proc.kill();
1194
1215
  } catch {}
1195
1216
  },
1217
+ killTree() {
1218
+ return killProcessTree(proc.pid);
1219
+ },
1196
1220
  get pid() {
1197
1221
  return proc.pid;
1198
1222
  },
@@ -1204,6 +1228,7 @@ function createNativePtySession(cmd, args, opts) {
1204
1228
  var cached = null, BACKEND_LOGGED;
1205
1229
  var init_pty = __esm(() => {
1206
1230
  init_resolve_executable();
1231
+ init_kill_tree();
1207
1232
  init_pty_sidecar();
1208
1233
  BACKEND_LOGGED = Symbol.for("runcastle.pty.backend.logged");
1209
1234
  });
@@ -1796,7 +1821,6 @@ var init_waypoints = __esm(() => {
1796
1821
  });
1797
1822
 
1798
1823
  // src/pty/dev-pane.ts
1799
- import { execFileSync } from "child_process";
1800
1824
  function drivePaneId(ownerId) {
1801
1825
  return `${DRIVE_PREFIX}${ownerId}`;
1802
1826
  }
@@ -1856,24 +1880,24 @@ function devPaneLive(paneId) {
1856
1880
  const entry = ptyRegistry().get(paneId);
1857
1881
  return !!entry && !entry.exited;
1858
1882
  }
1859
- function killProcessTree(pid) {
1860
- try {
1861
- if (process.platform === "win32") {
1862
- execFileSync("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
1863
- } else {
1864
- process.kill(-pid, "SIGTERM");
1865
- }
1866
- } catch {}
1883
+ function killTreeBounded(pty) {
1884
+ return new Promise((resolve4) => {
1885
+ const timer = setTimeout(resolve4, KILL_TREE_TIMEOUT_MS);
1886
+ pty.killTree().catch(() => {}).then(() => {
1887
+ clearTimeout(timer);
1888
+ resolve4();
1889
+ });
1890
+ });
1867
1891
  }
1868
- function stopDevPane(paneId) {
1892
+ async function stopDevPane(paneId) {
1869
1893
  const reg = ptyRegistry();
1870
1894
  const entry = reg.get(paneId);
1871
1895
  if (entry && !entry.exited)
1872
- killProcessTree(entry.pty.pid);
1896
+ await killTreeBounded(entry.pty);
1873
1897
  reg.kill(paneId);
1874
1898
  reg.remove(paneId);
1875
1899
  }
1876
- var DRIVE_PREFIX = "drive:", SNIFF_WINDOW = 8192;
1900
+ var DRIVE_PREFIX = "drive:", SNIFF_WINDOW = 8192, KILL_TREE_TIMEOUT_MS = 5000;
1877
1901
  var init_dev_pane = __esm(() => {
1878
1902
  init_events();
1879
1903
  init_registry();
@@ -2802,7 +2826,7 @@ async function testDrive(ctx, project, feature, action) {
2802
2826
  const detachedWorktree2 = testDriveState.detachedWorktree;
2803
2827
  const devPaneId = testDriveState.devPaneId;
2804
2828
  if (devPaneId)
2805
- stopDevPane(devPaneId);
2829
+ await stopDevPane(devPaneId);
2806
2830
  const teardown = await runDriveHookStep(ctx, scope, project.repoPath, "teardown", project.driveStopCommand, driveEnvFor(ctx, project, { slug: feature.slug, branch }, scope).env);
2807
2831
  const carriedChanges = await dirtyPaths(g);
2808
2832
  await g.checkout(previousBranch2);
@@ -2942,7 +2966,7 @@ async function startDryRun(ctx, project) {
2942
2966
  async function stopDryRun(ctx, project, state) {
2943
2967
  const scope = { projectId: project.id };
2944
2968
  if (state.devPaneId)
2945
- stopDevPane(state.devPaneId);
2969
+ await stopDevPane(state.devPaneId);
2946
2970
  const teardown = await runDriveHookStep(ctx, scope, project.repoPath, "teardown", project.driveStopCommand, state.env);
2947
2971
  state.observed.teardownOk = teardown?.result.ok;
2948
2972
  testDriveState = undefined;
@@ -3437,7 +3461,7 @@ function landProjectSession(ctx, session) {
3437
3461
  data: { sessionId: session.id, branch: PROJECT_BRANCH, ...data }
3438
3462
  });
3439
3463
  };
3440
- landProjectBranch(project).then((res) => {
3464
+ const landing = landProjectBranch(project).then((res) => {
3441
3465
  if (!res)
3442
3466
  return;
3443
3467
  if (res.landed) {
@@ -3450,6 +3474,8 @@ function landProjectSession(ctx, session) {
3450
3474
  }).catch((e) => {
3451
3475
  report("project.land_failed", `could not land the project session's work onto ${project.mainBranch}: ${e instanceof Error ? e.message : String(e)} \u2014 ${kept}`, { error: e instanceof Error ? e.message : String(e) });
3452
3476
  });
3477
+ inFlightLandings.add(landing);
3478
+ landing.finally(() => inFlightLandings.delete(landing));
3453
3479
  }
3454
3480
  function markSessionEnded(ctx, id) {
3455
3481
  const existing = getSessionRow(ctx, id);
@@ -3478,7 +3504,7 @@ function mostRecentLiveSession(ctx) {
3478
3504
  const row = ctx.db.select().from(sessions).where(eq5(sessions.status, "live")).orderBy(desc3(sql2`rowid`)).limit(1).get();
3479
3505
  return row ? rowToSession(row) : null;
3480
3506
  }
3481
- var KICKOFF_DELAY_MS = 1500, KICKOFF_SUBMIT_DELAY_MS = 350, KICKOFF_CONFIRM_MS = 12000, KICKOFF_MAX_ATTEMPTS = 3, CLEAR_INPUT = "\x15", SESSION_READY_TIMEOUT_MS = 25000, CONVERGE_KICKOFF_LINE, KICKOFF_LINES, RESUME_KICKOFF_PREFIX, pendingKickoffOverrides, deliveries, MATCH_PREFIX = 40, landedProjectSessions;
3507
+ var KICKOFF_DELAY_MS = 1500, KICKOFF_SUBMIT_DELAY_MS = 350, KICKOFF_CONFIRM_MS = 12000, KICKOFF_MAX_ATTEMPTS = 3, CLEAR_INPUT = "\x15", SESSION_READY_TIMEOUT_MS = 25000, CONVERGE_KICKOFF_LINE, KICKOFF_LINES, RESUME_KICKOFF_PREFIX, pendingKickoffOverrides, deliveries, MATCH_PREFIX = 40, landedProjectSessions, inFlightLandings;
3482
3508
  var init_sessions = __esm(() => {
3483
3509
  init_src();
3484
3510
  init_schema();
@@ -3501,6 +3527,7 @@ var init_sessions = __esm(() => {
3501
3527
  pendingKickoffOverrides = new Map;
3502
3528
  deliveries = new Map;
3503
3529
  landedProjectSessions = new Set;
3530
+ inFlightLandings = new Set;
3504
3531
  });
3505
3532
 
3506
3533
  // src/launcher/reconcile.ts
package/index.js CHANGED
@@ -913,6 +913,23 @@ var init_resolve_executable = __esm(() => {
913
913
  };
914
914
  });
915
915
 
916
+ // src/pty/kill-tree.ts
917
+ import { execFile } from "child_process";
918
+ import { promisify } from "util";
919
+ async function killProcessTree(pid) {
920
+ try {
921
+ if (process.platform === "win32") {
922
+ await execFileAsync("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true });
923
+ } else {
924
+ process.kill(-pid, "SIGTERM");
925
+ }
926
+ } catch {}
927
+ }
928
+ var execFileAsync;
929
+ var init_kill_tree = __esm(() => {
930
+ execFileAsync = promisify(execFile);
931
+ });
932
+
916
933
  // src/pty/pty-sidecar.ts
917
934
  import { spawn } from "child_process";
918
935
  import { createRequire } from "module";
@@ -1094,6 +1111,9 @@ ${why.replace(/\n/g, `\r
1094
1111
  }
1095
1112
  }, 500);
1096
1113
  },
1114
+ killTree() {
1115
+ return killProcessTree(process.platform === "win32" ? child.pid ?? pid : pid);
1116
+ },
1097
1117
  get pid() {
1098
1118
  return pid;
1099
1119
  },
@@ -1106,6 +1126,7 @@ var HOST_PATH;
1106
1126
  var init_pty_sidecar = __esm(() => {
1107
1127
  init_asset_paths();
1108
1128
  init_resolve_executable();
1129
+ init_kill_tree();
1109
1130
  HOST_PATH = resolveAsset(ASSET_ENV.ptyHost, fileURLToPath(new URL("./pty-host.cjs", import.meta.url)));
1110
1131
  });
1111
1132
 
@@ -1192,6 +1213,9 @@ function createNativePtySession(cmd, args, opts) {
1192
1213
  proc.kill();
1193
1214
  } catch {}
1194
1215
  },
1216
+ killTree() {
1217
+ return killProcessTree(proc.pid);
1218
+ },
1195
1219
  get pid() {
1196
1220
  return proc.pid;
1197
1221
  },
@@ -1203,6 +1227,7 @@ function createNativePtySession(cmd, args, opts) {
1203
1227
  var cached = null, BACKEND_LOGGED;
1204
1228
  var init_pty = __esm(() => {
1205
1229
  init_resolve_executable();
1230
+ init_kill_tree();
1206
1231
  init_pty_sidecar();
1207
1232
  BACKEND_LOGGED = Symbol.for("runcastle.pty.backend.logged");
1208
1233
  });
@@ -1795,7 +1820,6 @@ var init_waypoints = __esm(() => {
1795
1820
  });
1796
1821
 
1797
1822
  // src/pty/dev-pane.ts
1798
- import { execFileSync } from "child_process";
1799
1823
  function drivePaneId(ownerId) {
1800
1824
  return `${DRIVE_PREFIX}${ownerId}`;
1801
1825
  }
@@ -1855,24 +1879,24 @@ function devPaneLive(paneId) {
1855
1879
  const entry = ptyRegistry().get(paneId);
1856
1880
  return !!entry && !entry.exited;
1857
1881
  }
1858
- function killProcessTree(pid) {
1859
- try {
1860
- if (process.platform === "win32") {
1861
- execFileSync("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
1862
- } else {
1863
- process.kill(-pid, "SIGTERM");
1864
- }
1865
- } catch {}
1882
+ function killTreeBounded(pty) {
1883
+ return new Promise((resolve4) => {
1884
+ const timer = setTimeout(resolve4, KILL_TREE_TIMEOUT_MS);
1885
+ pty.killTree().catch(() => {}).then(() => {
1886
+ clearTimeout(timer);
1887
+ resolve4();
1888
+ });
1889
+ });
1866
1890
  }
1867
- function stopDevPane(paneId) {
1891
+ async function stopDevPane(paneId) {
1868
1892
  const reg = ptyRegistry();
1869
1893
  const entry = reg.get(paneId);
1870
1894
  if (entry && !entry.exited)
1871
- killProcessTree(entry.pty.pid);
1895
+ await killTreeBounded(entry.pty);
1872
1896
  reg.kill(paneId);
1873
1897
  reg.remove(paneId);
1874
1898
  }
1875
- var DRIVE_PREFIX = "drive:", SNIFF_WINDOW = 8192;
1899
+ var DRIVE_PREFIX = "drive:", SNIFF_WINDOW = 8192, KILL_TREE_TIMEOUT_MS = 5000;
1876
1900
  var init_dev_pane = __esm(() => {
1877
1901
  init_events();
1878
1902
  init_registry();
@@ -2801,7 +2825,7 @@ async function testDrive(ctx, project, feature, action) {
2801
2825
  const detachedWorktree2 = testDriveState.detachedWorktree;
2802
2826
  const devPaneId = testDriveState.devPaneId;
2803
2827
  if (devPaneId)
2804
- stopDevPane(devPaneId);
2828
+ await stopDevPane(devPaneId);
2805
2829
  const teardown = await runDriveHookStep(ctx, scope, project.repoPath, "teardown", project.driveStopCommand, driveEnvFor(ctx, project, { slug: feature.slug, branch }, scope).env);
2806
2830
  const carriedChanges = await dirtyPaths(g);
2807
2831
  await g.checkout(previousBranch2);
@@ -2941,7 +2965,7 @@ async function startDryRun(ctx, project) {
2941
2965
  async function stopDryRun(ctx, project, state) {
2942
2966
  const scope = { projectId: project.id };
2943
2967
  if (state.devPaneId)
2944
- stopDevPane(state.devPaneId);
2968
+ await stopDevPane(state.devPaneId);
2945
2969
  const teardown = await runDriveHookStep(ctx, scope, project.repoPath, "teardown", project.driveStopCommand, state.env);
2946
2970
  state.observed.teardownOk = teardown?.result.ok;
2947
2971
  testDriveState = undefined;
@@ -3436,7 +3460,7 @@ function landProjectSession(ctx, session) {
3436
3460
  data: { sessionId: session.id, branch: PROJECT_BRANCH, ...data }
3437
3461
  });
3438
3462
  };
3439
- landProjectBranch(project).then((res) => {
3463
+ const landing = landProjectBranch(project).then((res) => {
3440
3464
  if (!res)
3441
3465
  return;
3442
3466
  if (res.landed) {
@@ -3449,6 +3473,8 @@ function landProjectSession(ctx, session) {
3449
3473
  }).catch((e) => {
3450
3474
  report("project.land_failed", `could not land the project session's work onto ${project.mainBranch}: ${e instanceof Error ? e.message : String(e)} \u2014 ${kept}`, { error: e instanceof Error ? e.message : String(e) });
3451
3475
  });
3476
+ inFlightLandings.add(landing);
3477
+ landing.finally(() => inFlightLandings.delete(landing));
3452
3478
  }
3453
3479
  function markSessionEnded(ctx, id) {
3454
3480
  const existing = getSessionRow(ctx, id);
@@ -3477,7 +3503,7 @@ function mostRecentLiveSession(ctx) {
3477
3503
  const row = ctx.db.select().from(sessions).where(eq5(sessions.status, "live")).orderBy(desc3(sql2`rowid`)).limit(1).get();
3478
3504
  return row ? rowToSession(row) : null;
3479
3505
  }
3480
- var KICKOFF_DELAY_MS = 1500, KICKOFF_SUBMIT_DELAY_MS = 350, KICKOFF_CONFIRM_MS = 12000, KICKOFF_MAX_ATTEMPTS = 3, CLEAR_INPUT = "\x15", SESSION_READY_TIMEOUT_MS = 25000, CONVERGE_KICKOFF_LINE, KICKOFF_LINES, RESUME_KICKOFF_PREFIX, pendingKickoffOverrides, deliveries, MATCH_PREFIX = 40, landedProjectSessions;
3506
+ var KICKOFF_DELAY_MS = 1500, KICKOFF_SUBMIT_DELAY_MS = 350, KICKOFF_CONFIRM_MS = 12000, KICKOFF_MAX_ATTEMPTS = 3, CLEAR_INPUT = "\x15", SESSION_READY_TIMEOUT_MS = 25000, CONVERGE_KICKOFF_LINE, KICKOFF_LINES, RESUME_KICKOFF_PREFIX, pendingKickoffOverrides, deliveries, MATCH_PREFIX = 40, landedProjectSessions, inFlightLandings;
3481
3507
  var init_sessions = __esm(() => {
3482
3508
  init_src();
3483
3509
  init_schema();
@@ -3500,6 +3526,7 @@ var init_sessions = __esm(() => {
3500
3526
  pendingKickoffOverrides = new Map;
3501
3527
  deliveries = new Map;
3502
3528
  landedProjectSessions = new Set;
3529
+ inFlightLandings = new Set;
3503
3530
  });
3504
3531
 
3505
3532
  // src/launcher/reconcile.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runcastle",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "Burn tickets into shipped features with Claude Code.",
5
5
  "license": "MIT",
6
6
  "type": "module",