@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.
- package/dist/{chunk-VQDYONM4.js → chunk-72XUASTH.js} +142 -51
- package/dist/{chunk-EGKSFWVZ.js → chunk-WHYJL7CP.js} +130 -52
- package/dist/index.cjs +142 -49
- package/dist/index.d.cts +24 -3
- package/dist/index.d.ts +24 -3
- package/dist/index.js +5 -1
- package/dist/mcp/run-stdio.cjs +128 -50
- package/dist/mcp/run-stdio.js +1 -1
- package/dist/{orchestrator-OTEYZHTZ.js → orchestrator-6XEWQWSM.js} +1 -1
- package/dist/{orchestrator-IEU7U3RP.js → orchestrator-QUJORSTA.js} +1 -1
- package/package.json +1 -1
|
@@ -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-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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2441
|
-
const
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
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
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
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
|
|
2464
|
-
const
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
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-
|
|
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
|
-
...
|
|
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 :
|
|
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-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
const
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
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
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
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
|
|
2406
|
-
const
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
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-
|
|
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
|
-
...
|
|
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 :
|
|
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) {
|