@sideboard-ai/core 0.1.224 → 0.1.226
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/dist/{chunk-5L66IONI.js → chunk-EGKSFWVZ.js} +117 -26
- package/dist/{chunk-MPSXJ7WU.js → chunk-VQDYONM4.js} +118 -26
- package/dist/index.cjs +119 -26
- package/dist/index.d.cts +21 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +3 -1
- package/dist/mcp/run-stdio.cjs +120 -29
- package/dist/mcp/run-stdio.js +1 -1
- package/dist/{orchestrator-O3FNYIRE.js → orchestrator-IEU7U3RP.js} +1 -1
- package/dist/{orchestrator-ZAJ2M4UW.js → orchestrator-OTEYZHTZ.js} +1 -1
- package/package.json +1 -1
|
@@ -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-
|
|
421
|
+
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-OTEYZHTZ.js");
|
|
422
422
|
await getOrchestrator2().send(threadId, prompt);
|
|
423
423
|
} catch {
|
|
424
424
|
}
|
|
@@ -1197,6 +1197,7 @@ import {
|
|
|
1197
1197
|
readdirSync,
|
|
1198
1198
|
readFileSync as readFileSync4
|
|
1199
1199
|
} from "fs";
|
|
1200
|
+
import { execFileSync } from "child_process";
|
|
1200
1201
|
import { createServer } from "net";
|
|
1201
1202
|
import { basename, dirname as dirname2, join as join7 } from "path";
|
|
1202
1203
|
import { execa as execa2 } from "execa";
|
|
@@ -2177,6 +2178,34 @@ function pipeLines(stream, onLine) {
|
|
|
2177
2178
|
const rl = createInterface2({ input: stream });
|
|
2178
2179
|
rl.on("line", onLine);
|
|
2179
2180
|
}
|
|
2181
|
+
function killListenersOnPorts(ports) {
|
|
2182
|
+
for (const port of ports) {
|
|
2183
|
+
if (!Number.isFinite(port) || port <= 0) continue;
|
|
2184
|
+
try {
|
|
2185
|
+
if (process.platform === "win32") {
|
|
2186
|
+
execFileSync(
|
|
2187
|
+
"powershell",
|
|
2188
|
+
[
|
|
2189
|
+
"-NoProfile",
|
|
2190
|
+
"-Command",
|
|
2191
|
+
`Get-NetTCPConnection -LocalPort ${port} -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }`
|
|
2192
|
+
],
|
|
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" }
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
2205
|
+
} catch {
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2180
2209
|
function killScriptTree(child, ports = []) {
|
|
2181
2210
|
const pid = child.pid;
|
|
2182
2211
|
if (pid) {
|
|
@@ -2199,26 +2228,7 @@ function killScriptTree(child, ports = []) {
|
|
|
2199
2228
|
}
|
|
2200
2229
|
}
|
|
2201
2230
|
}
|
|
2202
|
-
|
|
2203
|
-
if (!Number.isFinite(port) || port <= 0) continue;
|
|
2204
|
-
if (process.platform === "win32") {
|
|
2205
|
-
void execa2(
|
|
2206
|
-
"powershell",
|
|
2207
|
-
[
|
|
2208
|
-
"-NoProfile",
|
|
2209
|
-
"-Command",
|
|
2210
|
-
`Get-NetTCPConnection -LocalPort ${port} -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }`
|
|
2211
|
-
],
|
|
2212
|
-
{ reject: false }
|
|
2213
|
-
);
|
|
2214
|
-
} else {
|
|
2215
|
-
void execa2(
|
|
2216
|
-
"zsh",
|
|
2217
|
-
["-lc", `pids=$(lsof -tiTCP:${port} -sTCP:LISTEN 2>/dev/null); [ -n "$pids" ] && kill -TERM $pids 2>/dev/null; true`],
|
|
2218
|
-
{ reject: false }
|
|
2219
|
-
);
|
|
2220
|
-
}
|
|
2221
|
-
}
|
|
2231
|
+
killListenersOnPorts(ports);
|
|
2222
2232
|
}
|
|
2223
2233
|
async function spawnWorkspaceScript(command, opts) {
|
|
2224
2234
|
const loginEnv = await captureLoginEnv();
|
|
@@ -4385,7 +4395,7 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
|
4385
4395
|
}
|
|
4386
4396
|
|
|
4387
4397
|
// src/threads/adopt.ts
|
|
4388
|
-
import { execFileSync } from "child_process";
|
|
4398
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
4389
4399
|
import {
|
|
4390
4400
|
copyFileSync as copyFileSync2,
|
|
4391
4401
|
existsSync as existsSync12,
|
|
@@ -4649,7 +4659,7 @@ function importConductorWorkspace(workspaceId) {
|
|
|
4649
4659
|
}
|
|
4650
4660
|
let repoPath = worktreePath;
|
|
4651
4661
|
try {
|
|
4652
|
-
repoPath =
|
|
4662
|
+
repoPath = execFileSync2("git", ["rev-parse", "--show-toplevel"], {
|
|
4653
4663
|
cwd: worktreePath,
|
|
4654
4664
|
encoding: "utf8"
|
|
4655
4665
|
}).trim();
|
|
@@ -6957,7 +6967,7 @@ function formatScheduledPrompt(name, prompt) {
|
|
|
6957
6967
|
${prompt}`;
|
|
6958
6968
|
}
|
|
6959
6969
|
async function defaultDeps() {
|
|
6960
|
-
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-
|
|
6970
|
+
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-OTEYZHTZ.js");
|
|
6961
6971
|
const orch = getOrchestrator2();
|
|
6962
6972
|
return {
|
|
6963
6973
|
findThread: (id) => findThreadByRef(id),
|
|
@@ -7094,6 +7104,16 @@ function writeLiveStatus(threadId, status, lastError) {
|
|
|
7094
7104
|
if (!latest || latest.status === "archived") return latest;
|
|
7095
7105
|
return setStatus(threadId, status, lastError);
|
|
7096
7106
|
}
|
|
7107
|
+
function collectActiveRunPorts(runs) {
|
|
7108
|
+
const ports = /* @__PURE__ */ new Set();
|
|
7109
|
+
for (const run2 of runs) {
|
|
7110
|
+
const list = run2.ports?.length ? run2.ports : run2.port != null ? [run2.port] : [];
|
|
7111
|
+
for (const p of list) {
|
|
7112
|
+
if (Number.isFinite(p) && p > 0) ports.add(p);
|
|
7113
|
+
}
|
|
7114
|
+
}
|
|
7115
|
+
return [...ports];
|
|
7116
|
+
}
|
|
7097
7117
|
var Orchestrator = class {
|
|
7098
7118
|
events = new EventEmitter();
|
|
7099
7119
|
processes = /* @__PURE__ */ new Map();
|
|
@@ -7235,6 +7255,9 @@ var Orchestrator = class {
|
|
|
7235
7255
|
this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
|
|
7236
7256
|
}
|
|
7237
7257
|
}
|
|
7258
|
+
if (reclaimStaleTurns) {
|
|
7259
|
+
this.reapOrphanedRunScripts();
|
|
7260
|
+
}
|
|
7238
7261
|
const repoPaths = (repoPath ? [repoPath] : [...new Set(listThreads({ includeArchived: true }).map((t) => t.repoPath))]).filter((p) => !isGlobalRepoPath(p));
|
|
7239
7262
|
await this.reconcileOrphans(repoPaths);
|
|
7240
7263
|
try {
|
|
@@ -8453,13 +8476,30 @@ var Orchestrator = class {
|
|
|
8453
8476
|
worktreeRunProcessKey(thread.worktreePath, scriptName),
|
|
8454
8477
|
...siblings.map((t) => `${t.id}:run:${scriptName}`)
|
|
8455
8478
|
];
|
|
8479
|
+
let hadHandle2 = false;
|
|
8456
8480
|
for (const key of keys) {
|
|
8457
8481
|
const proc = this.processes.get(key);
|
|
8458
|
-
if (proc)
|
|
8482
|
+
if (proc) {
|
|
8483
|
+
proc.kill();
|
|
8484
|
+
hadHandle2 = true;
|
|
8485
|
+
}
|
|
8459
8486
|
this.processes.delete(key);
|
|
8460
8487
|
}
|
|
8488
|
+
const legacyKey = worktreeDevProcessKey(thread.worktreePath);
|
|
8489
|
+
const legacy = this.processes.get(legacyKey);
|
|
8490
|
+
if (legacy?.scriptName === scriptName || !legacy?.scriptName && scriptName === "dev") {
|
|
8491
|
+
if (legacy && !hadHandle2) {
|
|
8492
|
+
legacy.kill();
|
|
8493
|
+
hadHandle2 = true;
|
|
8494
|
+
}
|
|
8495
|
+
this.processes.delete(legacyKey);
|
|
8496
|
+
}
|
|
8497
|
+
const run2 = shared.activeRuns.find((r) => r.scriptName === scriptName);
|
|
8498
|
+
if (!hadHandle2 && run2) {
|
|
8499
|
+
killListenersOnPorts(collectActiveRunPorts([run2]));
|
|
8500
|
+
}
|
|
8461
8501
|
const remaining = shared.activeRuns.filter((r) => r.scriptName !== scriptName);
|
|
8462
|
-
const isPrimary = shared.devPort != null &&
|
|
8502
|
+
const isPrimary = shared.devPort != null && run2?.port === shared.devPort;
|
|
8463
8503
|
this.syncWorktreeRuns(
|
|
8464
8504
|
thread.worktreePath,
|
|
8465
8505
|
remaining,
|
|
@@ -8468,15 +8508,66 @@ var Orchestrator = class {
|
|
|
8468
8508
|
this.emit({ type: "dev_server_stopped", threadId: thread.id, scriptName });
|
|
8469
8509
|
return;
|
|
8470
8510
|
}
|
|
8511
|
+
let hadHandle = false;
|
|
8471
8512
|
for (const [key, proc] of [...this.processes.entries()]) {
|
|
8472
8513
|
if (isWorktreeRunProcessKey(key, thread.worktreePath) || siblings.some((t) => key.startsWith(`${t.id}:run:`) || key === `${t.id}:dev`)) {
|
|
8473
8514
|
proc.kill();
|
|
8515
|
+
hadHandle = true;
|
|
8474
8516
|
this.processes.delete(key);
|
|
8475
8517
|
}
|
|
8476
8518
|
}
|
|
8519
|
+
if (!hadHandle && shared.activeRuns.length > 0) {
|
|
8520
|
+
killListenersOnPorts(collectActiveRunPorts(shared.activeRuns));
|
|
8521
|
+
}
|
|
8477
8522
|
this.syncWorktreeRuns(thread.worktreePath, [], null);
|
|
8478
8523
|
this.emit({ type: "dev_server_stopped", threadId: thread.id });
|
|
8479
8524
|
}
|
|
8525
|
+
/**
|
|
8526
|
+
* App quit: tear down every registered run/setup child and free persisted
|
|
8527
|
+
* ports so the next launch is not blocked by orphans.
|
|
8528
|
+
*/
|
|
8529
|
+
stopAllRunScripts() {
|
|
8530
|
+
for (const [key, proc] of [...this.processes.entries()]) {
|
|
8531
|
+
if (proc.kind !== "dev" && proc.kind !== "setup") continue;
|
|
8532
|
+
try {
|
|
8533
|
+
proc.kill();
|
|
8534
|
+
} catch {
|
|
8535
|
+
}
|
|
8536
|
+
this.processes.delete(key);
|
|
8537
|
+
}
|
|
8538
|
+
this.reapOrphanedRunScripts({ force: true });
|
|
8539
|
+
}
|
|
8540
|
+
/**
|
|
8541
|
+
* After restart (or force quit), in-memory handles are gone but `activeRuns`
|
|
8542
|
+
* and OS listeners may remain. Kill by port and clear disk metadata.
|
|
8543
|
+
* Skips worktrees that still have a live handle unless `force` (quit path).
|
|
8544
|
+
*/
|
|
8545
|
+
reapOrphanedRunScripts(opts) {
|
|
8546
|
+
const force = opts?.force === true;
|
|
8547
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8548
|
+
for (const thread of listThreads({ includeArchived: true })) {
|
|
8549
|
+
if (thread.status === "archived") continue;
|
|
8550
|
+
if (isGlobalThread(thread)) continue;
|
|
8551
|
+
const wt = normalizeWorktreePath(thread.worktreePath);
|
|
8552
|
+
if (seen.has(wt)) continue;
|
|
8553
|
+
seen.add(wt);
|
|
8554
|
+
const siblings = threadsSharingWorktree(thread.worktreePath);
|
|
8555
|
+
const shared = mergeWorktreeActiveRuns(siblings);
|
|
8556
|
+
if (shared.activeRuns.length === 0 && shared.devPort == null) continue;
|
|
8557
|
+
const hasLiveHandle = [...this.processes.keys()].some(
|
|
8558
|
+
(k) => isWorktreeRunProcessKey(k, thread.worktreePath) || siblings.some((t) => k.startsWith(`${t.id}:run:`) || k === `${t.id}:dev`)
|
|
8559
|
+
);
|
|
8560
|
+
if (!force && hasLiveHandle) continue;
|
|
8561
|
+
killListenersOnPorts(collectActiveRunPorts(shared.activeRuns));
|
|
8562
|
+
if (shared.devPort != null) {
|
|
8563
|
+
killListenersOnPorts([shared.devPort]);
|
|
8564
|
+
}
|
|
8565
|
+
this.syncWorktreeRuns(thread.worktreePath, [], null);
|
|
8566
|
+
for (const sibling of siblings) {
|
|
8567
|
+
this.emit({ type: "dev_server_stopped", threadId: sibling.id });
|
|
8568
|
+
}
|
|
8569
|
+
}
|
|
8570
|
+
}
|
|
8480
8571
|
syncWorktreeRuns(worktreePath, activeRuns, devPort) {
|
|
8481
8572
|
for (const sibling of threadsSharingWorktree(worktreePath)) {
|
|
8482
8573
|
updateThread(sibling.id, { activeRuns, devPort });
|
|
@@ -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-
|
|
485
|
+
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-IEU7U3RP.js");
|
|
486
486
|
await getOrchestrator2().send(threadId, prompt);
|
|
487
487
|
} catch {
|
|
488
488
|
}
|
|
@@ -1202,6 +1202,7 @@ import {
|
|
|
1202
1202
|
readdirSync,
|
|
1203
1203
|
readFileSync as readFileSync4
|
|
1204
1204
|
} from "fs";
|
|
1205
|
+
import { execFileSync } from "child_process";
|
|
1205
1206
|
import { createServer } from "net";
|
|
1206
1207
|
import { basename, dirname as dirname2, join as join7 } from "path";
|
|
1207
1208
|
import { execa as execa2 } from "execa";
|
|
@@ -2235,6 +2236,34 @@ function pipeLines(stream, onLine) {
|
|
|
2235
2236
|
const rl = createInterface2({ input: stream });
|
|
2236
2237
|
rl.on("line", onLine);
|
|
2237
2238
|
}
|
|
2239
|
+
function killListenersOnPorts(ports) {
|
|
2240
|
+
for (const port of ports) {
|
|
2241
|
+
if (!Number.isFinite(port) || port <= 0) continue;
|
|
2242
|
+
try {
|
|
2243
|
+
if (process.platform === "win32") {
|
|
2244
|
+
execFileSync(
|
|
2245
|
+
"powershell",
|
|
2246
|
+
[
|
|
2247
|
+
"-NoProfile",
|
|
2248
|
+
"-Command",
|
|
2249
|
+
`Get-NetTCPConnection -LocalPort ${port} -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }`
|
|
2250
|
+
],
|
|
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" }
|
|
2261
|
+
);
|
|
2262
|
+
}
|
|
2263
|
+
} catch {
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2238
2267
|
function killScriptTree(child, ports = []) {
|
|
2239
2268
|
const pid = child.pid;
|
|
2240
2269
|
if (pid) {
|
|
@@ -2257,26 +2286,7 @@ function killScriptTree(child, ports = []) {
|
|
|
2257
2286
|
}
|
|
2258
2287
|
}
|
|
2259
2288
|
}
|
|
2260
|
-
|
|
2261
|
-
if (!Number.isFinite(port) || port <= 0) continue;
|
|
2262
|
-
if (process.platform === "win32") {
|
|
2263
|
-
void execa2(
|
|
2264
|
-
"powershell",
|
|
2265
|
-
[
|
|
2266
|
-
"-NoProfile",
|
|
2267
|
-
"-Command",
|
|
2268
|
-
`Get-NetTCPConnection -LocalPort ${port} -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }`
|
|
2269
|
-
],
|
|
2270
|
-
{ reject: false }
|
|
2271
|
-
);
|
|
2272
|
-
} else {
|
|
2273
|
-
void execa2(
|
|
2274
|
-
"zsh",
|
|
2275
|
-
["-lc", `pids=$(lsof -tiTCP:${port} -sTCP:LISTEN 2>/dev/null); [ -n "$pids" ] && kill -TERM $pids 2>/dev/null; true`],
|
|
2276
|
-
{ reject: false }
|
|
2277
|
-
);
|
|
2278
|
-
}
|
|
2279
|
-
}
|
|
2289
|
+
killListenersOnPorts(ports);
|
|
2280
2290
|
}
|
|
2281
2291
|
async function spawnWorkspaceScript(command, opts) {
|
|
2282
2292
|
const loginEnv = await captureLoginEnv();
|
|
@@ -4554,7 +4564,7 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
|
4554
4564
|
}
|
|
4555
4565
|
|
|
4556
4566
|
// src/threads/adopt.ts
|
|
4557
|
-
import { execFileSync } from "child_process";
|
|
4567
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
4558
4568
|
import {
|
|
4559
4569
|
copyFileSync as copyFileSync2,
|
|
4560
4570
|
existsSync as existsSync12,
|
|
@@ -4821,7 +4831,7 @@ function importConductorWorkspace(workspaceId) {
|
|
|
4821
4831
|
}
|
|
4822
4832
|
let repoPath = worktreePath;
|
|
4823
4833
|
try {
|
|
4824
|
-
repoPath =
|
|
4834
|
+
repoPath = execFileSync2("git", ["rev-parse", "--show-toplevel"], {
|
|
4825
4835
|
cwd: worktreePath,
|
|
4826
4836
|
encoding: "utf8"
|
|
4827
4837
|
}).trim();
|
|
@@ -7332,7 +7342,7 @@ function formatScheduledPrompt(name, prompt) {
|
|
|
7332
7342
|
${prompt}`;
|
|
7333
7343
|
}
|
|
7334
7344
|
async function defaultDeps() {
|
|
7335
|
-
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-
|
|
7345
|
+
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-IEU7U3RP.js");
|
|
7336
7346
|
const orch = getOrchestrator2();
|
|
7337
7347
|
return {
|
|
7338
7348
|
findThread: (id) => findThreadByRef(id),
|
|
@@ -7469,6 +7479,16 @@ function writeLiveStatus(threadId, status, lastError) {
|
|
|
7469
7479
|
if (!latest || latest.status === "archived") return latest;
|
|
7470
7480
|
return setStatus(threadId, status, lastError);
|
|
7471
7481
|
}
|
|
7482
|
+
function collectActiveRunPorts(runs) {
|
|
7483
|
+
const ports = /* @__PURE__ */ new Set();
|
|
7484
|
+
for (const run2 of runs) {
|
|
7485
|
+
const list = run2.ports?.length ? run2.ports : run2.port != null ? [run2.port] : [];
|
|
7486
|
+
for (const p of list) {
|
|
7487
|
+
if (Number.isFinite(p) && p > 0) ports.add(p);
|
|
7488
|
+
}
|
|
7489
|
+
}
|
|
7490
|
+
return [...ports];
|
|
7491
|
+
}
|
|
7472
7492
|
var Orchestrator = class {
|
|
7473
7493
|
events = new EventEmitter();
|
|
7474
7494
|
processes = /* @__PURE__ */ new Map();
|
|
@@ -7610,6 +7630,9 @@ var Orchestrator = class {
|
|
|
7610
7630
|
this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
|
|
7611
7631
|
}
|
|
7612
7632
|
}
|
|
7633
|
+
if (reclaimStaleTurns) {
|
|
7634
|
+
this.reapOrphanedRunScripts();
|
|
7635
|
+
}
|
|
7613
7636
|
const repoPaths = (repoPath ? [repoPath] : [...new Set(listThreads({ includeArchived: true }).map((t) => t.repoPath))]).filter((p) => !isGlobalRepoPath(p));
|
|
7614
7637
|
await this.reconcileOrphans(repoPaths);
|
|
7615
7638
|
try {
|
|
@@ -8828,13 +8851,30 @@ var Orchestrator = class {
|
|
|
8828
8851
|
worktreeRunProcessKey(thread.worktreePath, scriptName),
|
|
8829
8852
|
...siblings.map((t) => `${t.id}:run:${scriptName}`)
|
|
8830
8853
|
];
|
|
8854
|
+
let hadHandle2 = false;
|
|
8831
8855
|
for (const key of keys) {
|
|
8832
8856
|
const proc = this.processes.get(key);
|
|
8833
|
-
if (proc)
|
|
8857
|
+
if (proc) {
|
|
8858
|
+
proc.kill();
|
|
8859
|
+
hadHandle2 = true;
|
|
8860
|
+
}
|
|
8834
8861
|
this.processes.delete(key);
|
|
8835
8862
|
}
|
|
8863
|
+
const legacyKey = worktreeDevProcessKey(thread.worktreePath);
|
|
8864
|
+
const legacy = this.processes.get(legacyKey);
|
|
8865
|
+
if (legacy?.scriptName === scriptName || !legacy?.scriptName && scriptName === "dev") {
|
|
8866
|
+
if (legacy && !hadHandle2) {
|
|
8867
|
+
legacy.kill();
|
|
8868
|
+
hadHandle2 = true;
|
|
8869
|
+
}
|
|
8870
|
+
this.processes.delete(legacyKey);
|
|
8871
|
+
}
|
|
8872
|
+
const run2 = shared.activeRuns.find((r) => r.scriptName === scriptName);
|
|
8873
|
+
if (!hadHandle2 && run2) {
|
|
8874
|
+
killListenersOnPorts(collectActiveRunPorts([run2]));
|
|
8875
|
+
}
|
|
8836
8876
|
const remaining = shared.activeRuns.filter((r) => r.scriptName !== scriptName);
|
|
8837
|
-
const isPrimary = shared.devPort != null &&
|
|
8877
|
+
const isPrimary = shared.devPort != null && run2?.port === shared.devPort;
|
|
8838
8878
|
this.syncWorktreeRuns(
|
|
8839
8879
|
thread.worktreePath,
|
|
8840
8880
|
remaining,
|
|
@@ -8843,15 +8883,66 @@ var Orchestrator = class {
|
|
|
8843
8883
|
this.emit({ type: "dev_server_stopped", threadId: thread.id, scriptName });
|
|
8844
8884
|
return;
|
|
8845
8885
|
}
|
|
8886
|
+
let hadHandle = false;
|
|
8846
8887
|
for (const [key, proc] of [...this.processes.entries()]) {
|
|
8847
8888
|
if (isWorktreeRunProcessKey(key, thread.worktreePath) || siblings.some((t) => key.startsWith(`${t.id}:run:`) || key === `${t.id}:dev`)) {
|
|
8848
8889
|
proc.kill();
|
|
8890
|
+
hadHandle = true;
|
|
8849
8891
|
this.processes.delete(key);
|
|
8850
8892
|
}
|
|
8851
8893
|
}
|
|
8894
|
+
if (!hadHandle && shared.activeRuns.length > 0) {
|
|
8895
|
+
killListenersOnPorts(collectActiveRunPorts(shared.activeRuns));
|
|
8896
|
+
}
|
|
8852
8897
|
this.syncWorktreeRuns(thread.worktreePath, [], null);
|
|
8853
8898
|
this.emit({ type: "dev_server_stopped", threadId: thread.id });
|
|
8854
8899
|
}
|
|
8900
|
+
/**
|
|
8901
|
+
* App quit: tear down every registered run/setup child and free persisted
|
|
8902
|
+
* ports so the next launch is not blocked by orphans.
|
|
8903
|
+
*/
|
|
8904
|
+
stopAllRunScripts() {
|
|
8905
|
+
for (const [key, proc] of [...this.processes.entries()]) {
|
|
8906
|
+
if (proc.kind !== "dev" && proc.kind !== "setup") continue;
|
|
8907
|
+
try {
|
|
8908
|
+
proc.kill();
|
|
8909
|
+
} catch {
|
|
8910
|
+
}
|
|
8911
|
+
this.processes.delete(key);
|
|
8912
|
+
}
|
|
8913
|
+
this.reapOrphanedRunScripts({ force: true });
|
|
8914
|
+
}
|
|
8915
|
+
/**
|
|
8916
|
+
* After restart (or force quit), in-memory handles are gone but `activeRuns`
|
|
8917
|
+
* and OS listeners may remain. Kill by port and clear disk metadata.
|
|
8918
|
+
* Skips worktrees that still have a live handle unless `force` (quit path).
|
|
8919
|
+
*/
|
|
8920
|
+
reapOrphanedRunScripts(opts) {
|
|
8921
|
+
const force = opts?.force === true;
|
|
8922
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8923
|
+
for (const thread of listThreads({ includeArchived: true })) {
|
|
8924
|
+
if (thread.status === "archived") continue;
|
|
8925
|
+
if (isGlobalThread(thread)) continue;
|
|
8926
|
+
const wt = normalizeWorktreePath(thread.worktreePath);
|
|
8927
|
+
if (seen.has(wt)) continue;
|
|
8928
|
+
seen.add(wt);
|
|
8929
|
+
const siblings = threadsSharingWorktree(thread.worktreePath);
|
|
8930
|
+
const shared = mergeWorktreeActiveRuns(siblings);
|
|
8931
|
+
if (shared.activeRuns.length === 0 && shared.devPort == null) continue;
|
|
8932
|
+
const hasLiveHandle = [...this.processes.keys()].some(
|
|
8933
|
+
(k) => isWorktreeRunProcessKey(k, thread.worktreePath) || siblings.some((t) => k.startsWith(`${t.id}:run:`) || k === `${t.id}:dev`)
|
|
8934
|
+
);
|
|
8935
|
+
if (!force && hasLiveHandle) continue;
|
|
8936
|
+
killListenersOnPorts(collectActiveRunPorts(shared.activeRuns));
|
|
8937
|
+
if (shared.devPort != null) {
|
|
8938
|
+
killListenersOnPorts([shared.devPort]);
|
|
8939
|
+
}
|
|
8940
|
+
this.syncWorktreeRuns(thread.worktreePath, [], null);
|
|
8941
|
+
for (const sibling of siblings) {
|
|
8942
|
+
this.emit({ type: "dev_server_stopped", threadId: sibling.id });
|
|
8943
|
+
}
|
|
8944
|
+
}
|
|
8945
|
+
}
|
|
8855
8946
|
syncWorktreeRuns(worktreePath, activeRuns, devPort) {
|
|
8856
8947
|
for (const sibling of threadsSharingWorktree(worktreePath)) {
|
|
8857
8948
|
updateThread(sibling.id, { activeRuns, devPort });
|
|
@@ -9924,6 +10015,7 @@ export {
|
|
|
9924
10015
|
copyConfiguredFiles,
|
|
9925
10016
|
captureLoginEnv,
|
|
9926
10017
|
buildWorkspaceScriptEnv,
|
|
10018
|
+
killListenersOnPorts,
|
|
9927
10019
|
runSetupScript,
|
|
9928
10020
|
runConventionSetup,
|
|
9929
10021
|
runWorkspaceSetup,
|