@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.
- package/dist/{chunk-5L66IONI.js → chunk-FUDJ6NFM.js} +142 -26
- package/dist/{chunk-MPSXJ7WU.js → chunk-I2HOATRJ.js} +143 -26
- package/dist/index.cjs +144 -26
- package/dist/index.d.cts +22 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +3 -1
- package/dist/mcp/run-stdio.cjs +145 -29
- package/dist/mcp/run-stdio.js +1 -1
- package/dist/{orchestrator-ZAJ2M4UW.js → orchestrator-CWOSAYAE.js} +1 -1
- package/dist/{orchestrator-O3FNYIRE.js → orchestrator-VKPHI64Q.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-CWOSAYAE.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;
|
|
@@ -1197,6 +1203,7 @@ import {
|
|
|
1197
1203
|
readdirSync,
|
|
1198
1204
|
readFileSync as readFileSync4
|
|
1199
1205
|
} from "fs";
|
|
1206
|
+
import { execFileSync } from "child_process";
|
|
1200
1207
|
import { createServer } from "net";
|
|
1201
1208
|
import { basename, dirname as dirname2, join as join7 } from "path";
|
|
1202
1209
|
import { execa as execa2 } from "execa";
|
|
@@ -2177,6 +2184,44 @@ function pipeLines(stream, onLine) {
|
|
|
2177
2184
|
const rl = createInterface2({ input: stream });
|
|
2178
2185
|
rl.on("line", onLine);
|
|
2179
2186
|
}
|
|
2187
|
+
function killListenersOnPorts(ports) {
|
|
2188
|
+
for (const port of ports) {
|
|
2189
|
+
if (!Number.isFinite(port) || port <= 0) continue;
|
|
2190
|
+
try {
|
|
2191
|
+
if (process.platform === "win32") {
|
|
2192
|
+
execFileSync(
|
|
2193
|
+
"powershell",
|
|
2194
|
+
[
|
|
2195
|
+
"-NoProfile",
|
|
2196
|
+
"-Command",
|
|
2197
|
+
`Get-NetTCPConnection -LocalPort ${port} -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }`
|
|
2198
|
+
],
|
|
2199
|
+
{ stdio: "ignore", timeout: 3e3 }
|
|
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
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
} catch {
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2180
2225
|
function killScriptTree(child, ports = []) {
|
|
2181
2226
|
const pid = child.pid;
|
|
2182
2227
|
if (pid) {
|
|
@@ -2199,26 +2244,7 @@ function killScriptTree(child, ports = []) {
|
|
|
2199
2244
|
}
|
|
2200
2245
|
}
|
|
2201
2246
|
}
|
|
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
|
-
}
|
|
2247
|
+
killListenersOnPorts(ports);
|
|
2222
2248
|
}
|
|
2223
2249
|
async function spawnWorkspaceScript(command, opts) {
|
|
2224
2250
|
const loginEnv = await captureLoginEnv();
|
|
@@ -4385,7 +4411,7 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
|
4385
4411
|
}
|
|
4386
4412
|
|
|
4387
4413
|
// src/threads/adopt.ts
|
|
4388
|
-
import { execFileSync } from "child_process";
|
|
4414
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
4389
4415
|
import {
|
|
4390
4416
|
copyFileSync as copyFileSync2,
|
|
4391
4417
|
existsSync as existsSync12,
|
|
@@ -4649,7 +4675,7 @@ function importConductorWorkspace(workspaceId) {
|
|
|
4649
4675
|
}
|
|
4650
4676
|
let repoPath = worktreePath;
|
|
4651
4677
|
try {
|
|
4652
|
-
repoPath =
|
|
4678
|
+
repoPath = execFileSync2("git", ["rev-parse", "--show-toplevel"], {
|
|
4653
4679
|
cwd: worktreePath,
|
|
4654
4680
|
encoding: "utf8"
|
|
4655
4681
|
}).trim();
|
|
@@ -6957,7 +6983,7 @@ function formatScheduledPrompt(name, prompt) {
|
|
|
6957
6983
|
${prompt}`;
|
|
6958
6984
|
}
|
|
6959
6985
|
async function defaultDeps() {
|
|
6960
|
-
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-
|
|
6986
|
+
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-CWOSAYAE.js");
|
|
6961
6987
|
const orch = getOrchestrator2();
|
|
6962
6988
|
return {
|
|
6963
6989
|
findThread: (id) => findThreadByRef(id),
|
|
@@ -7094,6 +7120,16 @@ function writeLiveStatus(threadId, status, lastError) {
|
|
|
7094
7120
|
if (!latest || latest.status === "archived") return latest;
|
|
7095
7121
|
return setStatus(threadId, status, lastError);
|
|
7096
7122
|
}
|
|
7123
|
+
function collectActiveRunPorts(runs) {
|
|
7124
|
+
const ports = /* @__PURE__ */ new Set();
|
|
7125
|
+
for (const run2 of runs) {
|
|
7126
|
+
const list = run2.ports?.length ? run2.ports : run2.port != null ? [run2.port] : [];
|
|
7127
|
+
for (const p of list) {
|
|
7128
|
+
if (Number.isFinite(p) && p > 0) ports.add(p);
|
|
7129
|
+
}
|
|
7130
|
+
}
|
|
7131
|
+
return [...ports];
|
|
7132
|
+
}
|
|
7097
7133
|
var Orchestrator = class {
|
|
7098
7134
|
events = new EventEmitter();
|
|
7099
7135
|
processes = /* @__PURE__ */ new Map();
|
|
@@ -7235,6 +7271,9 @@ var Orchestrator = class {
|
|
|
7235
7271
|
this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
|
|
7236
7272
|
}
|
|
7237
7273
|
}
|
|
7274
|
+
if (reclaimStaleTurns) {
|
|
7275
|
+
this.reapOrphanedRunScripts();
|
|
7276
|
+
}
|
|
7238
7277
|
const repoPaths = (repoPath ? [repoPath] : [...new Set(listThreads({ includeArchived: true }).map((t) => t.repoPath))]).filter((p) => !isGlobalRepoPath(p));
|
|
7239
7278
|
await this.reconcileOrphans(repoPaths);
|
|
7240
7279
|
try {
|
|
@@ -8453,13 +8492,30 @@ var Orchestrator = class {
|
|
|
8453
8492
|
worktreeRunProcessKey(thread.worktreePath, scriptName),
|
|
8454
8493
|
...siblings.map((t) => `${t.id}:run:${scriptName}`)
|
|
8455
8494
|
];
|
|
8495
|
+
let hadHandle2 = false;
|
|
8456
8496
|
for (const key of keys) {
|
|
8457
8497
|
const proc = this.processes.get(key);
|
|
8458
|
-
if (proc)
|
|
8498
|
+
if (proc) {
|
|
8499
|
+
proc.kill();
|
|
8500
|
+
hadHandle2 = true;
|
|
8501
|
+
}
|
|
8459
8502
|
this.processes.delete(key);
|
|
8460
8503
|
}
|
|
8504
|
+
const legacyKey = worktreeDevProcessKey(thread.worktreePath);
|
|
8505
|
+
const legacy = this.processes.get(legacyKey);
|
|
8506
|
+
if (legacy?.scriptName === scriptName || !legacy?.scriptName && scriptName === "dev") {
|
|
8507
|
+
if (legacy && !hadHandle2) {
|
|
8508
|
+
legacy.kill();
|
|
8509
|
+
hadHandle2 = true;
|
|
8510
|
+
}
|
|
8511
|
+
this.processes.delete(legacyKey);
|
|
8512
|
+
}
|
|
8513
|
+
const run2 = shared.activeRuns.find((r) => r.scriptName === scriptName);
|
|
8514
|
+
if (!hadHandle2 && run2) {
|
|
8515
|
+
killListenersOnPorts(collectActiveRunPorts([run2]));
|
|
8516
|
+
}
|
|
8461
8517
|
const remaining = shared.activeRuns.filter((r) => r.scriptName !== scriptName);
|
|
8462
|
-
const isPrimary = shared.devPort != null &&
|
|
8518
|
+
const isPrimary = shared.devPort != null && run2?.port === shared.devPort;
|
|
8463
8519
|
this.syncWorktreeRuns(
|
|
8464
8520
|
thread.worktreePath,
|
|
8465
8521
|
remaining,
|
|
@@ -8468,15 +8524,66 @@ var Orchestrator = class {
|
|
|
8468
8524
|
this.emit({ type: "dev_server_stopped", threadId: thread.id, scriptName });
|
|
8469
8525
|
return;
|
|
8470
8526
|
}
|
|
8527
|
+
let hadHandle = false;
|
|
8471
8528
|
for (const [key, proc] of [...this.processes.entries()]) {
|
|
8472
8529
|
if (isWorktreeRunProcessKey(key, thread.worktreePath) || siblings.some((t) => key.startsWith(`${t.id}:run:`) || key === `${t.id}:dev`)) {
|
|
8473
8530
|
proc.kill();
|
|
8531
|
+
hadHandle = true;
|
|
8474
8532
|
this.processes.delete(key);
|
|
8475
8533
|
}
|
|
8476
8534
|
}
|
|
8535
|
+
if (!hadHandle && shared.activeRuns.length > 0) {
|
|
8536
|
+
killListenersOnPorts(collectActiveRunPorts(shared.activeRuns));
|
|
8537
|
+
}
|
|
8477
8538
|
this.syncWorktreeRuns(thread.worktreePath, [], null);
|
|
8478
8539
|
this.emit({ type: "dev_server_stopped", threadId: thread.id });
|
|
8479
8540
|
}
|
|
8541
|
+
/**
|
|
8542
|
+
* App quit: tear down every registered run/setup child and free persisted
|
|
8543
|
+
* ports so the next launch is not blocked by orphans.
|
|
8544
|
+
*/
|
|
8545
|
+
stopAllRunScripts() {
|
|
8546
|
+
for (const [key, proc] of [...this.processes.entries()]) {
|
|
8547
|
+
if (proc.kind !== "dev" && proc.kind !== "setup") continue;
|
|
8548
|
+
try {
|
|
8549
|
+
proc.kill();
|
|
8550
|
+
} catch {
|
|
8551
|
+
}
|
|
8552
|
+
this.processes.delete(key);
|
|
8553
|
+
}
|
|
8554
|
+
this.reapOrphanedRunScripts({ force: true });
|
|
8555
|
+
}
|
|
8556
|
+
/**
|
|
8557
|
+
* After restart (or force quit), in-memory handles are gone but `activeRuns`
|
|
8558
|
+
* and OS listeners may remain. Kill by port and clear disk metadata.
|
|
8559
|
+
* Skips worktrees that still have a live handle unless `force` (quit path).
|
|
8560
|
+
*/
|
|
8561
|
+
reapOrphanedRunScripts(opts) {
|
|
8562
|
+
const force = opts?.force === true;
|
|
8563
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8564
|
+
for (const thread of listThreads({ includeArchived: true })) {
|
|
8565
|
+
if (thread.status === "archived") continue;
|
|
8566
|
+
if (isGlobalThread(thread)) continue;
|
|
8567
|
+
const wt = normalizeWorktreePath(thread.worktreePath);
|
|
8568
|
+
if (seen.has(wt)) continue;
|
|
8569
|
+
seen.add(wt);
|
|
8570
|
+
const siblings = threadsSharingWorktree(thread.worktreePath);
|
|
8571
|
+
const shared = mergeWorktreeActiveRuns(siblings);
|
|
8572
|
+
if (shared.activeRuns.length === 0 && shared.devPort == null) continue;
|
|
8573
|
+
const hasLiveHandle = [...this.processes.keys()].some(
|
|
8574
|
+
(k) => isWorktreeRunProcessKey(k, thread.worktreePath) || siblings.some((t) => k.startsWith(`${t.id}:run:`) || k === `${t.id}:dev`)
|
|
8575
|
+
);
|
|
8576
|
+
if (!force && hasLiveHandle) continue;
|
|
8577
|
+
killListenersOnPorts(collectActiveRunPorts(shared.activeRuns));
|
|
8578
|
+
if (shared.devPort != null) {
|
|
8579
|
+
killListenersOnPorts([shared.devPort]);
|
|
8580
|
+
}
|
|
8581
|
+
this.syncWorktreeRuns(thread.worktreePath, [], null);
|
|
8582
|
+
for (const sibling of siblings) {
|
|
8583
|
+
this.emit({ type: "dev_server_stopped", threadId: sibling.id });
|
|
8584
|
+
}
|
|
8585
|
+
}
|
|
8586
|
+
}
|
|
8480
8587
|
syncWorktreeRuns(worktreePath, activeRuns, devPort) {
|
|
8481
8588
|
for (const sibling of threadsSharingWorktree(worktreePath)) {
|
|
8482
8589
|
updateThread(sibling.id, { activeRuns, devPort });
|
|
@@ -8832,6 +8939,9 @@ var Orchestrator = class {
|
|
|
8832
8939
|
this.assertNotGlobal(thread, "Land");
|
|
8833
8940
|
const result = await confirmLand(thread, opts);
|
|
8834
8941
|
if (result.prUrl) {
|
|
8942
|
+
if (shouldStopRunOnPrRetarget(thread.prUrl, result.prUrl)) {
|
|
8943
|
+
this.stopDev(thread.id);
|
|
8944
|
+
}
|
|
8835
8945
|
const patch = { prUrl: result.prUrl };
|
|
8836
8946
|
try {
|
|
8837
8947
|
const meta = await getPrMeta(thread.worktreePath, result.prUrl);
|
|
@@ -8941,6 +9051,9 @@ var Orchestrator = class {
|
|
|
8941
9051
|
async persistPrMetaAndMaybeArchive(thread, meta) {
|
|
8942
9052
|
const prevState = normalizePrState(thread.prState);
|
|
8943
9053
|
const nextState = normalizePrState(meta.state);
|
|
9054
|
+
if (shouldStopRunOnPrRetarget(thread.prUrl, meta.url)) {
|
|
9055
|
+
this.stopDev(thread.id);
|
|
9056
|
+
}
|
|
8944
9057
|
const siblings = threadsSharingWorktree(thread.worktreePath);
|
|
8945
9058
|
const targets = siblings.length > 0 ? siblings : [thread];
|
|
8946
9059
|
for (const t of targets) {
|
|
@@ -8983,6 +9096,9 @@ var Orchestrator = class {
|
|
|
8983
9096
|
if (current?.position != null && thread.stackLayer !== current.position) {
|
|
8984
9097
|
patch.stackLayer = current.position;
|
|
8985
9098
|
}
|
|
9099
|
+
if (current?.prUrl && shouldStopRunOnPrRetarget(thread.prUrl, current.prUrl)) {
|
|
9100
|
+
this.stopDev(thread.id);
|
|
9101
|
+
}
|
|
8986
9102
|
if (current?.prUrl && current.prUrl !== thread.prUrl) patch.prUrl = current.prUrl;
|
|
8987
9103
|
if (current?.title && current.title !== thread.prTitle) patch.prTitle = current.title;
|
|
8988
9104
|
if (current?.branchName && current.branchName !== thread.branchName) {
|
|
@@ -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-VKPHI64Q.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;
|
|
@@ -1202,6 +1208,7 @@ import {
|
|
|
1202
1208
|
readdirSync,
|
|
1203
1209
|
readFileSync as readFileSync4
|
|
1204
1210
|
} from "fs";
|
|
1211
|
+
import { execFileSync } from "child_process";
|
|
1205
1212
|
import { createServer } from "net";
|
|
1206
1213
|
import { basename, dirname as dirname2, join as join7 } from "path";
|
|
1207
1214
|
import { execa as execa2 } from "execa";
|
|
@@ -2235,6 +2242,44 @@ function pipeLines(stream, onLine) {
|
|
|
2235
2242
|
const rl = createInterface2({ input: stream });
|
|
2236
2243
|
rl.on("line", onLine);
|
|
2237
2244
|
}
|
|
2245
|
+
function killListenersOnPorts(ports) {
|
|
2246
|
+
for (const port of ports) {
|
|
2247
|
+
if (!Number.isFinite(port) || port <= 0) continue;
|
|
2248
|
+
try {
|
|
2249
|
+
if (process.platform === "win32") {
|
|
2250
|
+
execFileSync(
|
|
2251
|
+
"powershell",
|
|
2252
|
+
[
|
|
2253
|
+
"-NoProfile",
|
|
2254
|
+
"-Command",
|
|
2255
|
+
`Get-NetTCPConnection -LocalPort ${port} -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }`
|
|
2256
|
+
],
|
|
2257
|
+
{ stdio: "ignore", timeout: 3e3 }
|
|
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
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
} catch {
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2238
2283
|
function killScriptTree(child, ports = []) {
|
|
2239
2284
|
const pid = child.pid;
|
|
2240
2285
|
if (pid) {
|
|
@@ -2257,26 +2302,7 @@ function killScriptTree(child, ports = []) {
|
|
|
2257
2302
|
}
|
|
2258
2303
|
}
|
|
2259
2304
|
}
|
|
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
|
-
}
|
|
2305
|
+
killListenersOnPorts(ports);
|
|
2280
2306
|
}
|
|
2281
2307
|
async function spawnWorkspaceScript(command, opts) {
|
|
2282
2308
|
const loginEnv = await captureLoginEnv();
|
|
@@ -4554,7 +4580,7 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
|
4554
4580
|
}
|
|
4555
4581
|
|
|
4556
4582
|
// src/threads/adopt.ts
|
|
4557
|
-
import { execFileSync } from "child_process";
|
|
4583
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
4558
4584
|
import {
|
|
4559
4585
|
copyFileSync as copyFileSync2,
|
|
4560
4586
|
existsSync as existsSync12,
|
|
@@ -4821,7 +4847,7 @@ function importConductorWorkspace(workspaceId) {
|
|
|
4821
4847
|
}
|
|
4822
4848
|
let repoPath = worktreePath;
|
|
4823
4849
|
try {
|
|
4824
|
-
repoPath =
|
|
4850
|
+
repoPath = execFileSync2("git", ["rev-parse", "--show-toplevel"], {
|
|
4825
4851
|
cwd: worktreePath,
|
|
4826
4852
|
encoding: "utf8"
|
|
4827
4853
|
}).trim();
|
|
@@ -7332,7 +7358,7 @@ function formatScheduledPrompt(name, prompt) {
|
|
|
7332
7358
|
${prompt}`;
|
|
7333
7359
|
}
|
|
7334
7360
|
async function defaultDeps() {
|
|
7335
|
-
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-
|
|
7361
|
+
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-VKPHI64Q.js");
|
|
7336
7362
|
const orch = getOrchestrator2();
|
|
7337
7363
|
return {
|
|
7338
7364
|
findThread: (id) => findThreadByRef(id),
|
|
@@ -7469,6 +7495,16 @@ function writeLiveStatus(threadId, status, lastError) {
|
|
|
7469
7495
|
if (!latest || latest.status === "archived") return latest;
|
|
7470
7496
|
return setStatus(threadId, status, lastError);
|
|
7471
7497
|
}
|
|
7498
|
+
function collectActiveRunPorts(runs) {
|
|
7499
|
+
const ports = /* @__PURE__ */ new Set();
|
|
7500
|
+
for (const run2 of runs) {
|
|
7501
|
+
const list = run2.ports?.length ? run2.ports : run2.port != null ? [run2.port] : [];
|
|
7502
|
+
for (const p of list) {
|
|
7503
|
+
if (Number.isFinite(p) && p > 0) ports.add(p);
|
|
7504
|
+
}
|
|
7505
|
+
}
|
|
7506
|
+
return [...ports];
|
|
7507
|
+
}
|
|
7472
7508
|
var Orchestrator = class {
|
|
7473
7509
|
events = new EventEmitter();
|
|
7474
7510
|
processes = /* @__PURE__ */ new Map();
|
|
@@ -7610,6 +7646,9 @@ var Orchestrator = class {
|
|
|
7610
7646
|
this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
|
|
7611
7647
|
}
|
|
7612
7648
|
}
|
|
7649
|
+
if (reclaimStaleTurns) {
|
|
7650
|
+
this.reapOrphanedRunScripts();
|
|
7651
|
+
}
|
|
7613
7652
|
const repoPaths = (repoPath ? [repoPath] : [...new Set(listThreads({ includeArchived: true }).map((t) => t.repoPath))]).filter((p) => !isGlobalRepoPath(p));
|
|
7614
7653
|
await this.reconcileOrphans(repoPaths);
|
|
7615
7654
|
try {
|
|
@@ -8828,13 +8867,30 @@ var Orchestrator = class {
|
|
|
8828
8867
|
worktreeRunProcessKey(thread.worktreePath, scriptName),
|
|
8829
8868
|
...siblings.map((t) => `${t.id}:run:${scriptName}`)
|
|
8830
8869
|
];
|
|
8870
|
+
let hadHandle2 = false;
|
|
8831
8871
|
for (const key of keys) {
|
|
8832
8872
|
const proc = this.processes.get(key);
|
|
8833
|
-
if (proc)
|
|
8873
|
+
if (proc) {
|
|
8874
|
+
proc.kill();
|
|
8875
|
+
hadHandle2 = true;
|
|
8876
|
+
}
|
|
8834
8877
|
this.processes.delete(key);
|
|
8835
8878
|
}
|
|
8879
|
+
const legacyKey = worktreeDevProcessKey(thread.worktreePath);
|
|
8880
|
+
const legacy = this.processes.get(legacyKey);
|
|
8881
|
+
if (legacy?.scriptName === scriptName || !legacy?.scriptName && scriptName === "dev") {
|
|
8882
|
+
if (legacy && !hadHandle2) {
|
|
8883
|
+
legacy.kill();
|
|
8884
|
+
hadHandle2 = true;
|
|
8885
|
+
}
|
|
8886
|
+
this.processes.delete(legacyKey);
|
|
8887
|
+
}
|
|
8888
|
+
const run2 = shared.activeRuns.find((r) => r.scriptName === scriptName);
|
|
8889
|
+
if (!hadHandle2 && run2) {
|
|
8890
|
+
killListenersOnPorts(collectActiveRunPorts([run2]));
|
|
8891
|
+
}
|
|
8836
8892
|
const remaining = shared.activeRuns.filter((r) => r.scriptName !== scriptName);
|
|
8837
|
-
const isPrimary = shared.devPort != null &&
|
|
8893
|
+
const isPrimary = shared.devPort != null && run2?.port === shared.devPort;
|
|
8838
8894
|
this.syncWorktreeRuns(
|
|
8839
8895
|
thread.worktreePath,
|
|
8840
8896
|
remaining,
|
|
@@ -8843,15 +8899,66 @@ var Orchestrator = class {
|
|
|
8843
8899
|
this.emit({ type: "dev_server_stopped", threadId: thread.id, scriptName });
|
|
8844
8900
|
return;
|
|
8845
8901
|
}
|
|
8902
|
+
let hadHandle = false;
|
|
8846
8903
|
for (const [key, proc] of [...this.processes.entries()]) {
|
|
8847
8904
|
if (isWorktreeRunProcessKey(key, thread.worktreePath) || siblings.some((t) => key.startsWith(`${t.id}:run:`) || key === `${t.id}:dev`)) {
|
|
8848
8905
|
proc.kill();
|
|
8906
|
+
hadHandle = true;
|
|
8849
8907
|
this.processes.delete(key);
|
|
8850
8908
|
}
|
|
8851
8909
|
}
|
|
8910
|
+
if (!hadHandle && shared.activeRuns.length > 0) {
|
|
8911
|
+
killListenersOnPorts(collectActiveRunPorts(shared.activeRuns));
|
|
8912
|
+
}
|
|
8852
8913
|
this.syncWorktreeRuns(thread.worktreePath, [], null);
|
|
8853
8914
|
this.emit({ type: "dev_server_stopped", threadId: thread.id });
|
|
8854
8915
|
}
|
|
8916
|
+
/**
|
|
8917
|
+
* App quit: tear down every registered run/setup child and free persisted
|
|
8918
|
+
* ports so the next launch is not blocked by orphans.
|
|
8919
|
+
*/
|
|
8920
|
+
stopAllRunScripts() {
|
|
8921
|
+
for (const [key, proc] of [...this.processes.entries()]) {
|
|
8922
|
+
if (proc.kind !== "dev" && proc.kind !== "setup") continue;
|
|
8923
|
+
try {
|
|
8924
|
+
proc.kill();
|
|
8925
|
+
} catch {
|
|
8926
|
+
}
|
|
8927
|
+
this.processes.delete(key);
|
|
8928
|
+
}
|
|
8929
|
+
this.reapOrphanedRunScripts({ force: true });
|
|
8930
|
+
}
|
|
8931
|
+
/**
|
|
8932
|
+
* After restart (or force quit), in-memory handles are gone but `activeRuns`
|
|
8933
|
+
* and OS listeners may remain. Kill by port and clear disk metadata.
|
|
8934
|
+
* Skips worktrees that still have a live handle unless `force` (quit path).
|
|
8935
|
+
*/
|
|
8936
|
+
reapOrphanedRunScripts(opts) {
|
|
8937
|
+
const force = opts?.force === true;
|
|
8938
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8939
|
+
for (const thread of listThreads({ includeArchived: true })) {
|
|
8940
|
+
if (thread.status === "archived") continue;
|
|
8941
|
+
if (isGlobalThread(thread)) continue;
|
|
8942
|
+
const wt = normalizeWorktreePath(thread.worktreePath);
|
|
8943
|
+
if (seen.has(wt)) continue;
|
|
8944
|
+
seen.add(wt);
|
|
8945
|
+
const siblings = threadsSharingWorktree(thread.worktreePath);
|
|
8946
|
+
const shared = mergeWorktreeActiveRuns(siblings);
|
|
8947
|
+
if (shared.activeRuns.length === 0 && shared.devPort == null) continue;
|
|
8948
|
+
const hasLiveHandle = [...this.processes.keys()].some(
|
|
8949
|
+
(k) => isWorktreeRunProcessKey(k, thread.worktreePath) || siblings.some((t) => k.startsWith(`${t.id}:run:`) || k === `${t.id}:dev`)
|
|
8950
|
+
);
|
|
8951
|
+
if (!force && hasLiveHandle) continue;
|
|
8952
|
+
killListenersOnPorts(collectActiveRunPorts(shared.activeRuns));
|
|
8953
|
+
if (shared.devPort != null) {
|
|
8954
|
+
killListenersOnPorts([shared.devPort]);
|
|
8955
|
+
}
|
|
8956
|
+
this.syncWorktreeRuns(thread.worktreePath, [], null);
|
|
8957
|
+
for (const sibling of siblings) {
|
|
8958
|
+
this.emit({ type: "dev_server_stopped", threadId: sibling.id });
|
|
8959
|
+
}
|
|
8960
|
+
}
|
|
8961
|
+
}
|
|
8855
8962
|
syncWorktreeRuns(worktreePath, activeRuns, devPort) {
|
|
8856
8963
|
for (const sibling of threadsSharingWorktree(worktreePath)) {
|
|
8857
8964
|
updateThread(sibling.id, { activeRuns, devPort });
|
|
@@ -9207,6 +9314,9 @@ var Orchestrator = class {
|
|
|
9207
9314
|
this.assertNotGlobal(thread, "Land");
|
|
9208
9315
|
const result = await confirmLand(thread, opts);
|
|
9209
9316
|
if (result.prUrl) {
|
|
9317
|
+
if (shouldStopRunOnPrRetarget(thread.prUrl, result.prUrl)) {
|
|
9318
|
+
this.stopDev(thread.id);
|
|
9319
|
+
}
|
|
9210
9320
|
const patch = { prUrl: result.prUrl };
|
|
9211
9321
|
try {
|
|
9212
9322
|
const meta = await getPrMeta(thread.worktreePath, result.prUrl);
|
|
@@ -9316,6 +9426,9 @@ var Orchestrator = class {
|
|
|
9316
9426
|
async persistPrMetaAndMaybeArchive(thread, meta) {
|
|
9317
9427
|
const prevState = normalizePrState(thread.prState);
|
|
9318
9428
|
const nextState = normalizePrState(meta.state);
|
|
9429
|
+
if (shouldStopRunOnPrRetarget(thread.prUrl, meta.url)) {
|
|
9430
|
+
this.stopDev(thread.id);
|
|
9431
|
+
}
|
|
9319
9432
|
const siblings = threadsSharingWorktree(thread.worktreePath);
|
|
9320
9433
|
const targets = siblings.length > 0 ? siblings : [thread];
|
|
9321
9434
|
for (const t of targets) {
|
|
@@ -9358,6 +9471,9 @@ var Orchestrator = class {
|
|
|
9358
9471
|
if (current?.position != null && thread.stackLayer !== current.position) {
|
|
9359
9472
|
patch.stackLayer = current.position;
|
|
9360
9473
|
}
|
|
9474
|
+
if (current?.prUrl && shouldStopRunOnPrRetarget(thread.prUrl, current.prUrl)) {
|
|
9475
|
+
this.stopDev(thread.id);
|
|
9476
|
+
}
|
|
9361
9477
|
if (current?.prUrl && current.prUrl !== thread.prUrl) patch.prUrl = current.prUrl;
|
|
9362
9478
|
if (current?.title && current.title !== thread.prTitle) patch.prTitle = current.title;
|
|
9363
9479
|
if (current?.branchName && current.branchName !== thread.branchName) {
|
|
@@ -9924,6 +10040,7 @@ export {
|
|
|
9924
10040
|
copyConfiguredFiles,
|
|
9925
10041
|
captureLoginEnv,
|
|
9926
10042
|
buildWorkspaceScriptEnv,
|
|
10043
|
+
killListenersOnPorts,
|
|
9927
10044
|
runSetupScript,
|
|
9928
10045
|
runConventionSetup,
|
|
9929
10046
|
runWorkspaceSetup,
|