@sideboard-ai/core 0.1.225 → 0.1.227

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.
@@ -13346,6 +13346,12 @@ function samePrIdentity(a, b) {
13346
13346
  const rn = right.match(/\/pull\/(\d+)/i)?.[1];
13347
13347
  return Boolean(ln && rn && ln === rn);
13348
13348
  }
13349
+ function shouldStopRunOnPrRetarget(previousPrUrl, nextPrUrl) {
13350
+ const prev = (previousPrUrl ?? "").trim();
13351
+ const next = (nextPrUrl ?? "").trim();
13352
+ if (!prev || !next) return false;
13353
+ return !samePrIdentity(prev, next);
13354
+ }
13349
13355
  function shouldPersistFetchedPrMeta(opts) {
13350
13356
  const meta = opts.metaUrl?.trim() || "";
13351
13357
  if (!meta) return false;
@@ -14429,6 +14435,44 @@ function pipeLines(stream, onLine) {
14429
14435
  const rl = (0, import_node_readline3.createInterface)({ input: stream });
14430
14436
  rl.on("line", onLine);
14431
14437
  }
14438
+ function killListenersOnPorts(ports) {
14439
+ for (const port of ports) {
14440
+ if (!Number.isFinite(port) || port <= 0) continue;
14441
+ try {
14442
+ if (process.platform === "win32") {
14443
+ (0, import_node_child_process4.execFileSync)(
14444
+ "powershell",
14445
+ [
14446
+ "-NoProfile",
14447
+ "-Command",
14448
+ `Get-NetTCPConnection -LocalPort ${port} -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }`
14449
+ ],
14450
+ { stdio: "ignore", timeout: 3e3 }
14451
+ );
14452
+ continue;
14453
+ }
14454
+ let raw = "";
14455
+ try {
14456
+ raw = (0, import_node_child_process4.execFileSync)("lsof", [`-tiTCP:${port}`, "-sTCP:LISTEN"], {
14457
+ encoding: "utf8",
14458
+ stdio: ["ignore", "pipe", "ignore"],
14459
+ timeout: 2e3
14460
+ });
14461
+ } catch {
14462
+ continue;
14463
+ }
14464
+ for (const token of raw.trim().split(/\s+/)) {
14465
+ const pid = Number(token);
14466
+ if (!Number.isFinite(pid) || pid <= 0) continue;
14467
+ try {
14468
+ process.kill(pid, "SIGTERM");
14469
+ } catch {
14470
+ }
14471
+ }
14472
+ } catch {
14473
+ }
14474
+ }
14475
+ }
14432
14476
  function killScriptTree(child, ports = []) {
14433
14477
  const pid = child.pid;
14434
14478
  if (pid) {
@@ -14451,26 +14495,7 @@ function killScriptTree(child, ports = []) {
14451
14495
  }
14452
14496
  }
14453
14497
  }
14454
- for (const port of ports) {
14455
- if (!Number.isFinite(port) || port <= 0) continue;
14456
- if (process.platform === "win32") {
14457
- void (0, import_execa4.execa)(
14458
- "powershell",
14459
- [
14460
- "-NoProfile",
14461
- "-Command",
14462
- `Get-NetTCPConnection -LocalPort ${port} -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }`
14463
- ],
14464
- { reject: false }
14465
- );
14466
- } else {
14467
- void (0, import_execa4.execa)(
14468
- "zsh",
14469
- ["-lc", `pids=$(lsof -tiTCP:${port} -sTCP:LISTEN 2>/dev/null); [ -n "$pids" ] && kill -TERM $pids 2>/dev/null; true`],
14470
- { reject: false }
14471
- );
14472
- }
14473
- }
14498
+ killListenersOnPorts(ports);
14474
14499
  }
14475
14500
  async function spawnWorkspaceScript(command, opts) {
14476
14501
  const loginEnv = await captureLoginEnv();
@@ -14661,11 +14686,12 @@ async function startDevServer(repoPath, worktreePath, onLine, opts) {
14661
14686
  done: handle.done
14662
14687
  };
14663
14688
  }
14664
- var import_node_fs37, import_node_net, import_node_path39, import_execa4, import_node_readline3, PORT_RANGE_SIZE, cachedLoginEnv;
14689
+ var import_node_fs37, import_node_child_process4, import_node_net, import_node_path39, import_execa4, import_node_readline3, PORT_RANGE_SIZE, cachedLoginEnv;
14665
14690
  var init_conductor = __esm({
14666
14691
  "src/hook/conductor.ts"() {
14667
14692
  "use strict";
14668
14693
  import_node_fs37 = require("fs");
14694
+ import_node_child_process4 = require("child_process");
14669
14695
  import_node_net = require("net");
14670
14696
  import_node_path39 = require("path");
14671
14697
  import_execa4 = require("execa");
@@ -18497,7 +18523,7 @@ function importConductorWorkspace(workspaceId) {
18497
18523
  }
18498
18524
  let repoPath = worktreePath;
18499
18525
  try {
18500
- repoPath = (0, import_node_child_process4.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
18526
+ repoPath = (0, import_node_child_process5.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
18501
18527
  cwd: worktreePath,
18502
18528
  encoding: "utf8"
18503
18529
  }).trim();
@@ -18526,11 +18552,11 @@ function importConductorWorkspace(workspaceId) {
18526
18552
  async function importConductorWorkspaceAsync(workspaceId) {
18527
18553
  return importConductorWorkspace(workspaceId);
18528
18554
  }
18529
- var import_node_child_process4, import_node_fs45, import_node_os14, import_node_path45, import_node_module4, CONDUCTOR_APP_SUPPORT, CONDUCTOR_DB, CURSOR_SDK_STORE;
18555
+ var import_node_child_process5, import_node_fs45, import_node_os14, import_node_path45, import_node_module4, CONDUCTOR_APP_SUPPORT, CONDUCTOR_DB, CURSOR_SDK_STORE;
18530
18556
  var init_adopt = __esm({
18531
18557
  "src/threads/adopt.ts"() {
18532
18558
  "use strict";
18533
- import_node_child_process4 = require("child_process");
18559
+ import_node_child_process5 = require("child_process");
18534
18560
  import_node_fs45 = require("fs");
18535
18561
  import_node_os14 = require("os");
18536
18562
  import_node_path45 = require("path");
@@ -20929,7 +20955,7 @@ function setCaffeinateHold(enabled, opts) {
20929
20955
  if (platform !== "darwin") {
20930
20956
  return idleState(platform);
20931
20957
  }
20932
- const spawnImpl = hooks.spawn ?? import_node_child_process5.spawn;
20958
+ const spawnImpl = hooks.spawn ?? import_node_child_process6.spawn;
20933
20959
  const child = spawnImpl("caffeinate", ["-dimsu"], {
20934
20960
  detached: true,
20935
20961
  stdio: "ignore"
@@ -20956,11 +20982,11 @@ function releaseCaffeinateHoldForThread(threadId) {
20956
20982
  }
20957
20983
  return setCaffeinateHold(false, { threadId: id });
20958
20984
  }
20959
- var import_node_child_process5, import_node_fs52, import_node_path51, hooks;
20985
+ var import_node_child_process6, import_node_fs52, import_node_path51, hooks;
20960
20986
  var init_caffeinate_hold = __esm({
20961
20987
  "src/store/caffeinate-hold.ts"() {
20962
20988
  "use strict";
20963
- import_node_child_process5 = require("child_process");
20989
+ import_node_child_process6 = require("child_process");
20964
20990
  import_node_fs52 = require("fs");
20965
20991
  import_node_path51 = require("path");
20966
20992
  init_paths();
@@ -21458,6 +21484,16 @@ function writeLiveStatus(threadId, status, lastError) {
21458
21484
  if (!latest || latest.status === "archived") return latest;
21459
21485
  return setStatus(threadId, status, lastError);
21460
21486
  }
21487
+ function collectActiveRunPorts(runs) {
21488
+ const ports = /* @__PURE__ */ new Set();
21489
+ for (const run2 of runs) {
21490
+ const list = run2.ports?.length ? run2.ports : run2.port != null ? [run2.port] : [];
21491
+ for (const p of list) {
21492
+ if (Number.isFinite(p) && p > 0) ports.add(p);
21493
+ }
21494
+ }
21495
+ return [...ports];
21496
+ }
21461
21497
  function getOrchestrator() {
21462
21498
  if (!singleton) {
21463
21499
  singleton = new Orchestrator();
@@ -21733,6 +21769,9 @@ var init_orchestrator = __esm({
21733
21769
  this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
21734
21770
  }
21735
21771
  }
21772
+ if (reclaimStaleTurns) {
21773
+ this.reapOrphanedRunScripts();
21774
+ }
21736
21775
  const repoPaths = (repoPath ? [repoPath] : [...new Set(listThreads({ includeArchived: true }).map((t) => t.repoPath))]).filter((p) => !isGlobalRepoPath(p));
21737
21776
  await this.reconcileOrphans(repoPaths);
21738
21777
  try {
@@ -22951,13 +22990,30 @@ var init_orchestrator = __esm({
22951
22990
  worktreeRunProcessKey(thread.worktreePath, scriptName),
22952
22991
  ...siblings.map((t) => `${t.id}:run:${scriptName}`)
22953
22992
  ];
22993
+ let hadHandle2 = false;
22954
22994
  for (const key of keys) {
22955
22995
  const proc = this.processes.get(key);
22956
- if (proc) proc.kill();
22996
+ if (proc) {
22997
+ proc.kill();
22998
+ hadHandle2 = true;
22999
+ }
22957
23000
  this.processes.delete(key);
22958
23001
  }
23002
+ const legacyKey = worktreeDevProcessKey(thread.worktreePath);
23003
+ const legacy = this.processes.get(legacyKey);
23004
+ if (legacy?.scriptName === scriptName || !legacy?.scriptName && scriptName === "dev") {
23005
+ if (legacy && !hadHandle2) {
23006
+ legacy.kill();
23007
+ hadHandle2 = true;
23008
+ }
23009
+ this.processes.delete(legacyKey);
23010
+ }
23011
+ const run2 = shared.activeRuns.find((r) => r.scriptName === scriptName);
23012
+ if (!hadHandle2 && run2) {
23013
+ killListenersOnPorts(collectActiveRunPorts([run2]));
23014
+ }
22959
23015
  const remaining = shared.activeRuns.filter((r) => r.scriptName !== scriptName);
22960
- const isPrimary = shared.devPort != null && shared.activeRuns.find((r) => r.scriptName === scriptName)?.port === shared.devPort;
23016
+ const isPrimary = shared.devPort != null && run2?.port === shared.devPort;
22961
23017
  this.syncWorktreeRuns(
22962
23018
  thread.worktreePath,
22963
23019
  remaining,
@@ -22966,15 +23022,66 @@ var init_orchestrator = __esm({
22966
23022
  this.emit({ type: "dev_server_stopped", threadId: thread.id, scriptName });
22967
23023
  return;
22968
23024
  }
23025
+ let hadHandle = false;
22969
23026
  for (const [key, proc] of [...this.processes.entries()]) {
22970
23027
  if (isWorktreeRunProcessKey(key, thread.worktreePath) || siblings.some((t) => key.startsWith(`${t.id}:run:`) || key === `${t.id}:dev`)) {
22971
23028
  proc.kill();
23029
+ hadHandle = true;
22972
23030
  this.processes.delete(key);
22973
23031
  }
22974
23032
  }
23033
+ if (!hadHandle && shared.activeRuns.length > 0) {
23034
+ killListenersOnPorts(collectActiveRunPorts(shared.activeRuns));
23035
+ }
22975
23036
  this.syncWorktreeRuns(thread.worktreePath, [], null);
22976
23037
  this.emit({ type: "dev_server_stopped", threadId: thread.id });
22977
23038
  }
23039
+ /**
23040
+ * App quit: tear down every registered run/setup child and free persisted
23041
+ * ports so the next launch is not blocked by orphans.
23042
+ */
23043
+ stopAllRunScripts() {
23044
+ for (const [key, proc] of [...this.processes.entries()]) {
23045
+ if (proc.kind !== "dev" && proc.kind !== "setup") continue;
23046
+ try {
23047
+ proc.kill();
23048
+ } catch {
23049
+ }
23050
+ this.processes.delete(key);
23051
+ }
23052
+ this.reapOrphanedRunScripts({ force: true });
23053
+ }
23054
+ /**
23055
+ * After restart (or force quit), in-memory handles are gone but `activeRuns`
23056
+ * and OS listeners may remain. Kill by port and clear disk metadata.
23057
+ * Skips worktrees that still have a live handle unless `force` (quit path).
23058
+ */
23059
+ reapOrphanedRunScripts(opts) {
23060
+ const force = opts?.force === true;
23061
+ const seen = /* @__PURE__ */ new Set();
23062
+ for (const thread of listThreads({ includeArchived: true })) {
23063
+ if (thread.status === "archived") continue;
23064
+ if (isGlobalThread(thread)) continue;
23065
+ const wt = normalizeWorktreePath(thread.worktreePath);
23066
+ if (seen.has(wt)) continue;
23067
+ seen.add(wt);
23068
+ const siblings = threadsSharingWorktree(thread.worktreePath);
23069
+ const shared = mergeWorktreeActiveRuns(siblings);
23070
+ if (shared.activeRuns.length === 0 && shared.devPort == null) continue;
23071
+ const hasLiveHandle = [...this.processes.keys()].some(
23072
+ (k) => isWorktreeRunProcessKey(k, thread.worktreePath) || siblings.some((t) => k.startsWith(`${t.id}:run:`) || k === `${t.id}:dev`)
23073
+ );
23074
+ if (!force && hasLiveHandle) continue;
23075
+ killListenersOnPorts(collectActiveRunPorts(shared.activeRuns));
23076
+ if (shared.devPort != null) {
23077
+ killListenersOnPorts([shared.devPort]);
23078
+ }
23079
+ this.syncWorktreeRuns(thread.worktreePath, [], null);
23080
+ for (const sibling of siblings) {
23081
+ this.emit({ type: "dev_server_stopped", threadId: sibling.id });
23082
+ }
23083
+ }
23084
+ }
22978
23085
  syncWorktreeRuns(worktreePath, activeRuns, devPort) {
22979
23086
  for (const sibling of threadsSharingWorktree(worktreePath)) {
22980
23087
  updateThread(sibling.id, { activeRuns, devPort });
@@ -23330,6 +23437,9 @@ var init_orchestrator = __esm({
23330
23437
  this.assertNotGlobal(thread, "Land");
23331
23438
  const result = await confirmLand(thread, opts);
23332
23439
  if (result.prUrl) {
23440
+ if (shouldStopRunOnPrRetarget(thread.prUrl, result.prUrl)) {
23441
+ this.stopDev(thread.id);
23442
+ }
23333
23443
  const patch = { prUrl: result.prUrl };
23334
23444
  try {
23335
23445
  const meta = await getPrMeta(thread.worktreePath, result.prUrl);
@@ -23439,6 +23549,9 @@ var init_orchestrator = __esm({
23439
23549
  async persistPrMetaAndMaybeArchive(thread, meta) {
23440
23550
  const prevState = normalizePrState(thread.prState);
23441
23551
  const nextState = normalizePrState(meta.state);
23552
+ if (shouldStopRunOnPrRetarget(thread.prUrl, meta.url)) {
23553
+ this.stopDev(thread.id);
23554
+ }
23442
23555
  const siblings = threadsSharingWorktree(thread.worktreePath);
23443
23556
  const targets = siblings.length > 0 ? siblings : [thread];
23444
23557
  for (const t of targets) {
@@ -23481,6 +23594,9 @@ var init_orchestrator = __esm({
23481
23594
  if (current?.position != null && thread.stackLayer !== current.position) {
23482
23595
  patch.stackLayer = current.position;
23483
23596
  }
23597
+ if (current?.prUrl && shouldStopRunOnPrRetarget(thread.prUrl, current.prUrl)) {
23598
+ this.stopDev(thread.id);
23599
+ }
23484
23600
  if (current?.prUrl && current.prUrl !== thread.prUrl) patch.prUrl = current.prUrl;
23485
23601
  if (current?.title && current.title !== thread.prTitle) patch.prTitle = current.title;
23486
23602
  if (current?.branchName && current.branchName !== thread.branchName) {
@@ -50,7 +50,7 @@ import {
50
50
  syncBoardPins,
51
51
  updateSchedule,
52
52
  waitForDetachedJob
53
- } from "../chunk-5L66IONI.js";
53
+ } from "../chunk-FUDJ6NFM.js";
54
54
  import "../chunk-JFOPOMK7.js";
55
55
  import "../chunk-YWI3IBP2.js";
56
56
  import {
@@ -10,7 +10,7 @@ import {
10
10
  resolveSendFollowUp,
11
11
  startOrchestration,
12
12
  waitForPidExit
13
- } from "./chunk-5L66IONI.js";
13
+ } from "./chunk-FUDJ6NFM.js";
14
14
  import "./chunk-JFOPOMK7.js";
15
15
  import "./chunk-YWI3IBP2.js";
16
16
  import "./chunk-SQHRPJ7P.js";
@@ -8,7 +8,7 @@ import {
8
8
  resolveSendFollowUp,
9
9
  startOrchestration,
10
10
  waitForPidExit
11
- } from "./chunk-MPSXJ7WU.js";
11
+ } from "./chunk-I2HOATRJ.js";
12
12
  import "./chunk-DJU54RYW.js";
13
13
  import "./chunk-VUW3VKLW.js";
14
14
  import "./chunk-N2VST3NL.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sideboard-ai/core",
3
- "version": "0.1.225",
3
+ "version": "0.1.227",
4
4
  "description": "Sideboard core — orchestration, agents, git worktrees, MCP server",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",