@sideboard-ai/core 0.1.226 → 0.1.228

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.
@@ -482,7 +482,7 @@ async function continueSourceThread(threadId, prompt) {
482
482
  await continueOnReply(threadId, prompt);
483
483
  return;
484
484
  }
485
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-IEU7U3RP.js");
485
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-QUJORSTA.js");
486
486
  await getOrchestrator2().send(threadId, prompt);
487
487
  } catch {
488
488
  }
@@ -1148,6 +1148,12 @@ function samePrIdentity(a, b) {
1148
1148
  const rn = right.match(/\/pull\/(\d+)/i)?.[1];
1149
1149
  return Boolean(ln && rn && ln === rn);
1150
1150
  }
1151
+ function shouldStopRunOnPrRetarget(previousPrUrl, nextPrUrl) {
1152
+ const prev = (previousPrUrl ?? "").trim();
1153
+ const next = (nextPrUrl ?? "").trim();
1154
+ if (!prev || !next) return false;
1155
+ return !samePrIdentity(prev, next);
1156
+ }
1151
1157
  function shouldPersistFetchedPrMeta(opts) {
1152
1158
  const meta = opts.metaUrl?.trim() || "";
1153
1159
  if (!meta) return false;
@@ -2248,17 +2254,27 @@ function killListenersOnPorts(ports) {
2248
2254
  "-Command",
2249
2255
  `Get-NetTCPConnection -LocalPort ${port} -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }`
2250
2256
  ],
2251
- { stdio: "ignore" }
2252
- );
2253
- } else {
2254
- execFileSync(
2255
- "zsh",
2256
- [
2257
- "-lc",
2258
- `pids=$(lsof -tiTCP:${port} -sTCP:LISTEN 2>/dev/null); [ -n "$pids" ] && kill -TERM $pids 2>/dev/null; true`
2259
- ],
2260
- { stdio: "ignore" }
2257
+ { stdio: "ignore", timeout: 3e3 }
2261
2258
  );
2259
+ continue;
2260
+ }
2261
+ let raw = "";
2262
+ try {
2263
+ raw = execFileSync("lsof", [`-tiTCP:${port}`, "-sTCP:LISTEN"], {
2264
+ encoding: "utf8",
2265
+ stdio: ["ignore", "pipe", "ignore"],
2266
+ timeout: 2e3
2267
+ });
2268
+ } catch {
2269
+ continue;
2270
+ }
2271
+ for (const token of raw.trim().split(/\s+/)) {
2272
+ const pid = Number(token);
2273
+ if (!Number.isFinite(pid) || pid <= 0) continue;
2274
+ try {
2275
+ process.kill(pid, "SIGTERM");
2276
+ } catch {
2277
+ }
2262
2278
  }
2263
2279
  } catch {
2264
2280
  }
@@ -2421,61 +2437,103 @@ function getRunScript(worktreePath, repoPath, name) {
2421
2437
  function getRunMode(worktreePath, repoPath) {
2422
2438
  return loadWorkspaceSettings(worktreePath, repoPath)?.runMode ?? "concurrent";
2423
2439
  }
2424
- async function allocatePort() {
2440
+ async function reservePort() {
2425
2441
  return new Promise((resolve, reject) => {
2426
2442
  const server = createServer();
2443
+ let released = false;
2444
+ const release = () => new Promise((res) => {
2445
+ if (released) {
2446
+ res();
2447
+ return;
2448
+ }
2449
+ released = true;
2450
+ server.close(() => res());
2451
+ });
2427
2452
  server.listen(0, "127.0.0.1", () => {
2428
2453
  const addr = server.address();
2429
2454
  if (!addr || typeof addr === "string") {
2430
- server.close();
2431
- reject(new Error("Failed to allocate port"));
2455
+ void release().then(() => reject(new Error("Failed to allocate port")));
2432
2456
  return;
2433
2457
  }
2434
- const port = addr.port;
2435
- server.close((err) => err ? reject(err) : resolve(port));
2458
+ resolve({ port: addr.port, release });
2436
2459
  });
2437
2460
  server.on("error", reject);
2438
2461
  });
2439
2462
  }
2440
- async function allocatePortRange(size = PORT_RANGE_SIZE) {
2441
- const base = await allocatePort();
2442
- const ports = [base];
2443
- for (let i = 1; i < size; i++) {
2444
- const candidate = base + i;
2445
- const free = await new Promise((resolve) => {
2446
- const server = createServer();
2447
- server.once("error", () => resolve(false));
2448
- server.listen(candidate, "127.0.0.1", () => {
2449
- server.close(() => resolve(true));
2450
- });
2463
+ async function allocatePort() {
2464
+ const held = await reservePort();
2465
+ await held.release();
2466
+ return held.port;
2467
+ }
2468
+ async function tryReservePort(port) {
2469
+ return new Promise((resolve) => {
2470
+ const server = createServer();
2471
+ let released = false;
2472
+ const release = () => new Promise((res) => {
2473
+ if (released) {
2474
+ res();
2475
+ return;
2476
+ }
2477
+ released = true;
2478
+ server.close(() => res());
2451
2479
  });
2452
- if (free) {
2453
- ports.push(candidate);
2454
- } else {
2455
- ports.push(await allocatePort());
2480
+ server.once("error", () => resolve(null));
2481
+ server.listen(port, "127.0.0.1", () => {
2482
+ resolve({ port, release });
2483
+ });
2484
+ });
2485
+ }
2486
+ async function reservePortRange(size = PORT_RANGE_SIZE) {
2487
+ const held = [await reservePort()];
2488
+ const base = held[0].port;
2489
+ try {
2490
+ for (let i = 1; i < size; i++) {
2491
+ const candidate = base + i;
2492
+ const next = await tryReservePort(candidate);
2493
+ if (next) {
2494
+ held.push(next);
2495
+ } else {
2496
+ held.push(await reservePort());
2497
+ }
2456
2498
  }
2499
+ return held;
2500
+ } catch (err) {
2501
+ await Promise.all(held.map((h) => h.release()));
2502
+ throw err;
2457
2503
  }
2504
+ }
2505
+ async function allocatePortRange(size = PORT_RANGE_SIZE) {
2506
+ const held = await reservePortRange(size);
2507
+ const ports = held.map((h) => h.port);
2508
+ await Promise.all(held.map((h) => h.release()));
2458
2509
  return ports;
2459
2510
  }
2460
2511
  async function startDevServer(repoPath, worktreePath, onLine, opts) {
2461
2512
  const script = getRunScript(worktreePath, repoPath, opts?.scriptName);
2462
2513
  if (!script) return null;
2463
- const ports = await allocatePortRange(PORT_RANGE_SIZE);
2464
- const handle = await spawnWorkspaceScript(script.command, {
2465
- worktreePath,
2466
- repoPath,
2467
- ports,
2468
- defaultBranch: opts?.defaultBranch,
2469
- onLine
2470
- });
2471
- return {
2472
- pid: handle.pid,
2473
- port: ports[0],
2474
- ports,
2475
- scriptName: script.name,
2476
- kill: handle.kill,
2477
- done: handle.done
2478
- };
2514
+ const held = await reservePortRange(PORT_RANGE_SIZE);
2515
+ const ports = held.map((h) => h.port);
2516
+ try {
2517
+ await Promise.all(held.map((h) => h.release()));
2518
+ const handle = await spawnWorkspaceScript(script.command, {
2519
+ worktreePath,
2520
+ repoPath,
2521
+ ports,
2522
+ defaultBranch: opts?.defaultBranch,
2523
+ onLine
2524
+ });
2525
+ return {
2526
+ pid: handle.pid,
2527
+ port: ports[0],
2528
+ ports,
2529
+ scriptName: script.name,
2530
+ kill: handle.kill,
2531
+ done: handle.done
2532
+ };
2533
+ } catch (err) {
2534
+ await Promise.all(held.map((h) => h.release()));
2535
+ throw err;
2536
+ }
2479
2537
  }
2480
2538
 
2481
2539
  // src/store/setup-log.ts
@@ -7342,7 +7400,7 @@ function formatScheduledPrompt(name, prompt) {
7342
7400
  ${prompt}`;
7343
7401
  }
7344
7402
  async function defaultDeps() {
7345
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-IEU7U3RP.js");
7403
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-QUJORSTA.js");
7346
7404
  const orch = getOrchestrator2();
7347
7405
  return {
7348
7406
  findThread: (id) => findThreadByRef(id),
@@ -7523,6 +7581,8 @@ var Orchestrator = class {
7523
7581
  /** Auto-continues after a worktree turn ended while a detached job still runs. */
7524
7582
  jobContinueCount = /* @__PURE__ */ new Map();
7525
7583
  jobContinueNudged = /* @__PURE__ */ new Set();
7584
+ /** In-flight startDev per worktree run key — agent MCP + UI Start must not double-spawn. */
7585
+ startingDev = /* @__PURE__ */ new Map();
7526
7586
  maxConcurrent;
7527
7587
  runningCount = 0;
7528
7588
  constructor(opts) {
@@ -8740,9 +8800,20 @@ var Orchestrator = class {
8740
8800
  "No run script found for this worktree. Add [scripts.run.*] in the worktree\u2019s .sideboard/settings.toml or .conductor/settings.toml (or settings.local.toml on the main repo)."
8741
8801
  );
8742
8802
  }
8803
+ const runKey = worktreeRunProcessKey(thread.worktreePath, resolvedName);
8804
+ const inFlight = this.startingDev.get(runKey);
8805
+ if (inFlight) return inFlight;
8806
+ const start = this.startDevUnlocked(thread, resolvedName, scriptName, scripts, runKey);
8807
+ this.startingDev.set(runKey, start);
8808
+ try {
8809
+ return await start;
8810
+ } finally {
8811
+ this.startingDev.delete(runKey);
8812
+ }
8813
+ }
8814
+ async startDevUnlocked(thread, resolvedName, scriptName, scripts, runKey) {
8743
8815
  const siblings = threadsSharingWorktree(thread.worktreePath);
8744
8816
  const shared = mergeWorktreeActiveRuns(siblings);
8745
- const runKey = worktreeRunProcessKey(thread.worktreePath, resolvedName);
8746
8817
  const existing = this.processes.get(runKey) ?? siblings.map((t) => this.processes.get(`${t.id}:run:${resolvedName}`)).find(Boolean);
8747
8818
  const active = shared.activeRuns.find((r) => r.scriptName === resolvedName);
8748
8819
  if (existing && active) {
@@ -8754,6 +8825,14 @@ var Orchestrator = class {
8754
8825
  return { port: shared.devPort, scriptName: resolvedName, ports: [shared.devPort] };
8755
8826
  }
8756
8827
  }
8828
+ if (active && !existing) {
8829
+ killListenersOnPorts(collectActiveRunPorts([active]));
8830
+ this.syncWorktreeRuns(
8831
+ thread.worktreePath,
8832
+ shared.activeRuns.filter((r) => r.scriptName !== resolvedName),
8833
+ shared.devPort === active.port ? null : shared.devPort
8834
+ );
8835
+ }
8757
8836
  const mode = getRunMode(thread.worktreePath, thread.repoPath);
8758
8837
  if (mode === "nonconcurrent") {
8759
8838
  const here = normalizeWorktreePath(thread.worktreePath);
@@ -8802,6 +8881,7 @@ var Orchestrator = class {
8802
8881
  kill: handle.kill
8803
8882
  });
8804
8883
  }
8884
+ const latestShared = mergeWorktreeActiveRuns(threadsSharingWorktree(thread.worktreePath));
8805
8885
  const run2 = {
8806
8886
  scriptName: resolvedName,
8807
8887
  port: handle.port,
@@ -8809,13 +8889,13 @@ var Orchestrator = class {
8809
8889
  startedAt
8810
8890
  };
8811
8891
  const nextRuns = [
8812
- ...shared.activeRuns.filter((r) => r.scriptName !== resolvedName),
8892
+ ...latestShared.activeRuns.filter((r) => r.scriptName !== resolvedName),
8813
8893
  run2
8814
8894
  ];
8815
8895
  this.syncWorktreeRuns(
8816
8896
  thread.worktreePath,
8817
8897
  nextRuns,
8818
- isDefault ? handle.port : shared.devPort
8898
+ isDefault ? handle.port : latestShared.devPort
8819
8899
  );
8820
8900
  this.emit({
8821
8901
  type: "dev_server_started",
@@ -9298,6 +9378,9 @@ var Orchestrator = class {
9298
9378
  this.assertNotGlobal(thread, "Land");
9299
9379
  const result = await confirmLand(thread, opts);
9300
9380
  if (result.prUrl) {
9381
+ if (shouldStopRunOnPrRetarget(thread.prUrl, result.prUrl)) {
9382
+ this.stopDev(thread.id);
9383
+ }
9301
9384
  const patch = { prUrl: result.prUrl };
9302
9385
  try {
9303
9386
  const meta = await getPrMeta(thread.worktreePath, result.prUrl);
@@ -9407,6 +9490,9 @@ var Orchestrator = class {
9407
9490
  async persistPrMetaAndMaybeArchive(thread, meta) {
9408
9491
  const prevState = normalizePrState(thread.prState);
9409
9492
  const nextState = normalizePrState(meta.state);
9493
+ if (shouldStopRunOnPrRetarget(thread.prUrl, meta.url)) {
9494
+ this.stopDev(thread.id);
9495
+ }
9410
9496
  const siblings = threadsSharingWorktree(thread.worktreePath);
9411
9497
  const targets = siblings.length > 0 ? siblings : [thread];
9412
9498
  for (const t of targets) {
@@ -9449,6 +9535,9 @@ var Orchestrator = class {
9449
9535
  if (current?.position != null && thread.stackLayer !== current.position) {
9450
9536
  patch.stackLayer = current.position;
9451
9537
  }
9538
+ if (current?.prUrl && shouldStopRunOnPrRetarget(thread.prUrl, current.prUrl)) {
9539
+ this.stopDev(thread.id);
9540
+ }
9452
9541
  if (current?.prUrl && current.prUrl !== thread.prUrl) patch.prUrl = current.prUrl;
9453
9542
  if (current?.title && current.title !== thread.prTitle) patch.prTitle = current.title;
9454
9543
  if (current?.branchName && current.branchName !== thread.branchName) {
@@ -10024,7 +10113,9 @@ export {
10024
10113
  getDefaultRunScript,
10025
10114
  getRunScript,
10026
10115
  getRunMode,
10116
+ reservePort,
10027
10117
  allocatePort,
10118
+ reservePortRange,
10028
10119
  allocatePortRange,
10029
10120
  startDevServer,
10030
10121
  findOrphanWorktrees,
@@ -418,7 +418,7 @@ async function continueSourceThread(threadId, prompt) {
418
418
  await continueOnReply(threadId, prompt);
419
419
  return;
420
420
  }
421
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-OTEYZHTZ.js");
421
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-6XEWQWSM.js");
422
422
  await getOrchestrator2().send(threadId, prompt);
423
423
  } catch {
424
424
  }
@@ -1143,6 +1143,12 @@ function samePrIdentity(a, b) {
1143
1143
  const rn = right.match(/\/pull\/(\d+)/i)?.[1];
1144
1144
  return Boolean(ln && rn && ln === rn);
1145
1145
  }
1146
+ function shouldStopRunOnPrRetarget(previousPrUrl, nextPrUrl) {
1147
+ const prev = (previousPrUrl ?? "").trim();
1148
+ const next = (nextPrUrl ?? "").trim();
1149
+ if (!prev || !next) return false;
1150
+ return !samePrIdentity(prev, next);
1151
+ }
1146
1152
  function shouldPersistFetchedPrMeta(opts) {
1147
1153
  const meta = opts.metaUrl?.trim() || "";
1148
1154
  if (!meta) return false;
@@ -2190,17 +2196,27 @@ function killListenersOnPorts(ports) {
2190
2196
  "-Command",
2191
2197
  `Get-NetTCPConnection -LocalPort ${port} -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }`
2192
2198
  ],
2193
- { stdio: "ignore" }
2194
- );
2195
- } else {
2196
- execFileSync(
2197
- "zsh",
2198
- [
2199
- "-lc",
2200
- `pids=$(lsof -tiTCP:${port} -sTCP:LISTEN 2>/dev/null); [ -n "$pids" ] && kill -TERM $pids 2>/dev/null; true`
2201
- ],
2202
- { stdio: "ignore" }
2199
+ { stdio: "ignore", timeout: 3e3 }
2203
2200
  );
2201
+ continue;
2202
+ }
2203
+ let raw = "";
2204
+ try {
2205
+ raw = execFileSync("lsof", [`-tiTCP:${port}`, "-sTCP:LISTEN"], {
2206
+ encoding: "utf8",
2207
+ stdio: ["ignore", "pipe", "ignore"],
2208
+ timeout: 2e3
2209
+ });
2210
+ } catch {
2211
+ continue;
2212
+ }
2213
+ for (const token of raw.trim().split(/\s+/)) {
2214
+ const pid = Number(token);
2215
+ if (!Number.isFinite(pid) || pid <= 0) continue;
2216
+ try {
2217
+ process.kill(pid, "SIGTERM");
2218
+ } catch {
2219
+ }
2204
2220
  }
2205
2221
  } catch {
2206
2222
  }
@@ -2363,61 +2379,92 @@ function getRunScript(worktreePath, repoPath, name) {
2363
2379
  function getRunMode(worktreePath, repoPath) {
2364
2380
  return loadWorkspaceSettings(worktreePath, repoPath)?.runMode ?? "concurrent";
2365
2381
  }
2366
- async function allocatePort() {
2382
+ async function reservePort() {
2367
2383
  return new Promise((resolve, reject) => {
2368
2384
  const server = createServer();
2385
+ let released = false;
2386
+ const release = () => new Promise((res) => {
2387
+ if (released) {
2388
+ res();
2389
+ return;
2390
+ }
2391
+ released = true;
2392
+ server.close(() => res());
2393
+ });
2369
2394
  server.listen(0, "127.0.0.1", () => {
2370
2395
  const addr = server.address();
2371
2396
  if (!addr || typeof addr === "string") {
2372
- server.close();
2373
- reject(new Error("Failed to allocate port"));
2397
+ void release().then(() => reject(new Error("Failed to allocate port")));
2374
2398
  return;
2375
2399
  }
2376
- const port = addr.port;
2377
- server.close((err) => err ? reject(err) : resolve(port));
2400
+ resolve({ port: addr.port, release });
2378
2401
  });
2379
2402
  server.on("error", reject);
2380
2403
  });
2381
2404
  }
2382
- async function allocatePortRange(size = PORT_RANGE_SIZE) {
2383
- const base = await allocatePort();
2384
- const ports = [base];
2385
- for (let i = 1; i < size; i++) {
2386
- const candidate = base + i;
2387
- const free = await new Promise((resolve) => {
2388
- const server = createServer();
2389
- server.once("error", () => resolve(false));
2390
- server.listen(candidate, "127.0.0.1", () => {
2391
- server.close(() => resolve(true));
2392
- });
2405
+ async function tryReservePort(port) {
2406
+ return new Promise((resolve) => {
2407
+ const server = createServer();
2408
+ let released = false;
2409
+ const release = () => new Promise((res) => {
2410
+ if (released) {
2411
+ res();
2412
+ return;
2413
+ }
2414
+ released = true;
2415
+ server.close(() => res());
2393
2416
  });
2394
- if (free) {
2395
- ports.push(candidate);
2396
- } else {
2397
- ports.push(await allocatePort());
2417
+ server.once("error", () => resolve(null));
2418
+ server.listen(port, "127.0.0.1", () => {
2419
+ resolve({ port, release });
2420
+ });
2421
+ });
2422
+ }
2423
+ async function reservePortRange(size = PORT_RANGE_SIZE) {
2424
+ const held = [await reservePort()];
2425
+ const base = held[0].port;
2426
+ try {
2427
+ for (let i = 1; i < size; i++) {
2428
+ const candidate = base + i;
2429
+ const next = await tryReservePort(candidate);
2430
+ if (next) {
2431
+ held.push(next);
2432
+ } else {
2433
+ held.push(await reservePort());
2434
+ }
2398
2435
  }
2436
+ return held;
2437
+ } catch (err) {
2438
+ await Promise.all(held.map((h) => h.release()));
2439
+ throw err;
2399
2440
  }
2400
- return ports;
2401
2441
  }
2402
2442
  async function startDevServer(repoPath, worktreePath, onLine, opts) {
2403
2443
  const script = getRunScript(worktreePath, repoPath, opts?.scriptName);
2404
2444
  if (!script) return null;
2405
- const ports = await allocatePortRange(PORT_RANGE_SIZE);
2406
- const handle = await spawnWorkspaceScript(script.command, {
2407
- worktreePath,
2408
- repoPath,
2409
- ports,
2410
- defaultBranch: opts?.defaultBranch,
2411
- onLine
2412
- });
2413
- return {
2414
- pid: handle.pid,
2415
- port: ports[0],
2416
- ports,
2417
- scriptName: script.name,
2418
- kill: handle.kill,
2419
- done: handle.done
2420
- };
2445
+ const held = await reservePortRange(PORT_RANGE_SIZE);
2446
+ const ports = held.map((h) => h.port);
2447
+ try {
2448
+ await Promise.all(held.map((h) => h.release()));
2449
+ const handle = await spawnWorkspaceScript(script.command, {
2450
+ worktreePath,
2451
+ repoPath,
2452
+ ports,
2453
+ defaultBranch: opts?.defaultBranch,
2454
+ onLine
2455
+ });
2456
+ return {
2457
+ pid: handle.pid,
2458
+ port: ports[0],
2459
+ ports,
2460
+ scriptName: script.name,
2461
+ kill: handle.kill,
2462
+ done: handle.done
2463
+ };
2464
+ } catch (err) {
2465
+ await Promise.all(held.map((h) => h.release()));
2466
+ throw err;
2467
+ }
2421
2468
  }
2422
2469
 
2423
2470
  // src/store/setup-log.ts
@@ -6967,7 +7014,7 @@ function formatScheduledPrompt(name, prompt) {
6967
7014
  ${prompt}`;
6968
7015
  }
6969
7016
  async function defaultDeps() {
6970
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-OTEYZHTZ.js");
7017
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-6XEWQWSM.js");
6971
7018
  const orch = getOrchestrator2();
6972
7019
  return {
6973
7020
  findThread: (id) => findThreadByRef(id),
@@ -7148,6 +7195,8 @@ var Orchestrator = class {
7148
7195
  /** Auto-continues after a worktree turn ended while a detached job still runs. */
7149
7196
  jobContinueCount = /* @__PURE__ */ new Map();
7150
7197
  jobContinueNudged = /* @__PURE__ */ new Set();
7198
+ /** In-flight startDev per worktree run key — agent MCP + UI Start must not double-spawn. */
7199
+ startingDev = /* @__PURE__ */ new Map();
7151
7200
  maxConcurrent;
7152
7201
  runningCount = 0;
7153
7202
  constructor(opts) {
@@ -8365,9 +8414,20 @@ var Orchestrator = class {
8365
8414
  "No run script found for this worktree. Add [scripts.run.*] in the worktree\u2019s .sideboard/settings.toml or .conductor/settings.toml (or settings.local.toml on the main repo)."
8366
8415
  );
8367
8416
  }
8417
+ const runKey = worktreeRunProcessKey(thread.worktreePath, resolvedName);
8418
+ const inFlight = this.startingDev.get(runKey);
8419
+ if (inFlight) return inFlight;
8420
+ const start = this.startDevUnlocked(thread, resolvedName, scriptName, scripts, runKey);
8421
+ this.startingDev.set(runKey, start);
8422
+ try {
8423
+ return await start;
8424
+ } finally {
8425
+ this.startingDev.delete(runKey);
8426
+ }
8427
+ }
8428
+ async startDevUnlocked(thread, resolvedName, scriptName, scripts, runKey) {
8368
8429
  const siblings = threadsSharingWorktree(thread.worktreePath);
8369
8430
  const shared = mergeWorktreeActiveRuns(siblings);
8370
- const runKey = worktreeRunProcessKey(thread.worktreePath, resolvedName);
8371
8431
  const existing = this.processes.get(runKey) ?? siblings.map((t) => this.processes.get(`${t.id}:run:${resolvedName}`)).find(Boolean);
8372
8432
  const active = shared.activeRuns.find((r) => r.scriptName === resolvedName);
8373
8433
  if (existing && active) {
@@ -8379,6 +8439,14 @@ var Orchestrator = class {
8379
8439
  return { port: shared.devPort, scriptName: resolvedName, ports: [shared.devPort] };
8380
8440
  }
8381
8441
  }
8442
+ if (active && !existing) {
8443
+ killListenersOnPorts(collectActiveRunPorts([active]));
8444
+ this.syncWorktreeRuns(
8445
+ thread.worktreePath,
8446
+ shared.activeRuns.filter((r) => r.scriptName !== resolvedName),
8447
+ shared.devPort === active.port ? null : shared.devPort
8448
+ );
8449
+ }
8382
8450
  const mode = getRunMode(thread.worktreePath, thread.repoPath);
8383
8451
  if (mode === "nonconcurrent") {
8384
8452
  const here = normalizeWorktreePath(thread.worktreePath);
@@ -8427,6 +8495,7 @@ var Orchestrator = class {
8427
8495
  kill: handle.kill
8428
8496
  });
8429
8497
  }
8498
+ const latestShared = mergeWorktreeActiveRuns(threadsSharingWorktree(thread.worktreePath));
8430
8499
  const run2 = {
8431
8500
  scriptName: resolvedName,
8432
8501
  port: handle.port,
@@ -8434,13 +8503,13 @@ var Orchestrator = class {
8434
8503
  startedAt
8435
8504
  };
8436
8505
  const nextRuns = [
8437
- ...shared.activeRuns.filter((r) => r.scriptName !== resolvedName),
8506
+ ...latestShared.activeRuns.filter((r) => r.scriptName !== resolvedName),
8438
8507
  run2
8439
8508
  ];
8440
8509
  this.syncWorktreeRuns(
8441
8510
  thread.worktreePath,
8442
8511
  nextRuns,
8443
- isDefault ? handle.port : shared.devPort
8512
+ isDefault ? handle.port : latestShared.devPort
8444
8513
  );
8445
8514
  this.emit({
8446
8515
  type: "dev_server_started",
@@ -8923,6 +8992,9 @@ var Orchestrator = class {
8923
8992
  this.assertNotGlobal(thread, "Land");
8924
8993
  const result = await confirmLand(thread, opts);
8925
8994
  if (result.prUrl) {
8995
+ if (shouldStopRunOnPrRetarget(thread.prUrl, result.prUrl)) {
8996
+ this.stopDev(thread.id);
8997
+ }
8926
8998
  const patch = { prUrl: result.prUrl };
8927
8999
  try {
8928
9000
  const meta = await getPrMeta(thread.worktreePath, result.prUrl);
@@ -9032,6 +9104,9 @@ var Orchestrator = class {
9032
9104
  async persistPrMetaAndMaybeArchive(thread, meta) {
9033
9105
  const prevState = normalizePrState(thread.prState);
9034
9106
  const nextState = normalizePrState(meta.state);
9107
+ if (shouldStopRunOnPrRetarget(thread.prUrl, meta.url)) {
9108
+ this.stopDev(thread.id);
9109
+ }
9035
9110
  const siblings = threadsSharingWorktree(thread.worktreePath);
9036
9111
  const targets = siblings.length > 0 ? siblings : [thread];
9037
9112
  for (const t of targets) {
@@ -9074,6 +9149,9 @@ var Orchestrator = class {
9074
9149
  if (current?.position != null && thread.stackLayer !== current.position) {
9075
9150
  patch.stackLayer = current.position;
9076
9151
  }
9152
+ if (current?.prUrl && shouldStopRunOnPrRetarget(thread.prUrl, current.prUrl)) {
9153
+ this.stopDev(thread.id);
9154
+ }
9077
9155
  if (current?.prUrl && current.prUrl !== thread.prUrl) patch.prUrl = current.prUrl;
9078
9156
  if (current?.title && current.title !== thread.prTitle) patch.prTitle = current.title;
9079
9157
  if (current?.branchName && current.branchName !== thread.branchName) {