@sideboard-ai/core 0.1.238 → 0.1.239
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-E6UJGLPM.js → chunk-CDG4CLWQ.js} +326 -147
- package/dist/{chunk-32DG3DA2.js → chunk-D5SDZSEA.js} +337 -148
- package/dist/index.cjs +501 -295
- package/dist/index.d.cts +33 -2
- package/dist/index.d.ts +33 -2
- package/dist/index.js +22 -2
- package/dist/mcp/run-stdio.cjs +482 -296
- package/dist/mcp/run-stdio.js +2 -2
- package/dist/{orchestrator-AAYKR4AI.js → orchestrator-3ZP4WTQ4.js} +1 -1
- package/dist/{orchestrator-HPMDB2SG.js → orchestrator-R75VDUR2.js} +1 -1
- package/package.json +1 -1
|
@@ -745,13 +745,14 @@ function shouldAutoArchiveOnPrMerge(opts) {
|
|
|
745
745
|
import {
|
|
746
746
|
copyFileSync,
|
|
747
747
|
existsSync as existsSync3,
|
|
748
|
-
mkdirSync as
|
|
748
|
+
mkdirSync as mkdirSync4,
|
|
749
749
|
readdirSync,
|
|
750
|
-
readFileSync as
|
|
750
|
+
readFileSync as readFileSync3,
|
|
751
|
+
realpathSync
|
|
751
752
|
} from "fs";
|
|
752
753
|
import { execFileSync } from "child_process";
|
|
753
754
|
import { createServer } from "net";
|
|
754
|
-
import { basename, dirname as
|
|
755
|
+
import { basename, dirname as dirname3, join as join5, resolve, sep } from "path";
|
|
755
756
|
import { execa as execa2 } from "execa";
|
|
756
757
|
import { createInterface as createInterface2 } from "readline";
|
|
757
758
|
|
|
@@ -1664,16 +1665,73 @@ async function requestReview(threadRef, send) {
|
|
|
1664
1665
|
return { tab: started, from };
|
|
1665
1666
|
}
|
|
1666
1667
|
|
|
1668
|
+
// src/hook/worktree-run-ports.ts
|
|
1669
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1670
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
1671
|
+
var RUN_PORTS_REL = ".context/.sideboard/run-ports.json";
|
|
1672
|
+
function worktreeRunPortsPath(worktreePath) {
|
|
1673
|
+
return join4(worktreePath, RUN_PORTS_REL);
|
|
1674
|
+
}
|
|
1675
|
+
function isPort(value) {
|
|
1676
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 65535;
|
|
1677
|
+
}
|
|
1678
|
+
function parseFile(raw) {
|
|
1679
|
+
try {
|
|
1680
|
+
const parsed = JSON.parse(raw);
|
|
1681
|
+
if (!parsed || typeof parsed !== "object") return {};
|
|
1682
|
+
const scripts = parsed.scripts;
|
|
1683
|
+
if (!scripts || typeof scripts !== "object") return {};
|
|
1684
|
+
const next = {};
|
|
1685
|
+
for (const [name, ports] of Object.entries(scripts)) {
|
|
1686
|
+
if (!name || !Array.isArray(ports)) continue;
|
|
1687
|
+
const cleaned = ports.filter(isPort);
|
|
1688
|
+
if (cleaned.length) next[name] = cleaned;
|
|
1689
|
+
}
|
|
1690
|
+
return { scripts: next };
|
|
1691
|
+
} catch {
|
|
1692
|
+
return {};
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
function loadWorktreeRunPorts(worktreePath, scriptName) {
|
|
1696
|
+
if (!worktreePath.trim() || !scriptName.trim()) return null;
|
|
1697
|
+
try {
|
|
1698
|
+
const raw = readFileSync2(worktreeRunPortsPath(worktreePath), "utf8");
|
|
1699
|
+
const ports = parseFile(raw).scripts?.[scriptName];
|
|
1700
|
+
return ports?.length ? ports : null;
|
|
1701
|
+
} catch {
|
|
1702
|
+
return null;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
function saveWorktreeRunPorts(worktreePath, scriptName, ports) {
|
|
1706
|
+
if (!worktreePath.trim() || !scriptName.trim()) return;
|
|
1707
|
+
const cleaned = ports.filter(isPort);
|
|
1708
|
+
if (!cleaned.length) return;
|
|
1709
|
+
const path = worktreeRunPortsPath(worktreePath);
|
|
1710
|
+
let current = {};
|
|
1711
|
+
try {
|
|
1712
|
+
current = parseFile(readFileSync2(path, "utf8"));
|
|
1713
|
+
} catch {
|
|
1714
|
+
current = {};
|
|
1715
|
+
}
|
|
1716
|
+
const scripts = { ...current.scripts ?? {} };
|
|
1717
|
+
scripts[scriptName] = cleaned;
|
|
1718
|
+
mkdirSync3(dirname2(path), { recursive: true });
|
|
1719
|
+
writeFileSync2(path, `${JSON.stringify({ scripts }, null, 2)}
|
|
1720
|
+
`, "utf8");
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1667
1723
|
// src/hook/conductor.ts
|
|
1668
1724
|
var PORT_RANGE_SIZE = 10;
|
|
1725
|
+
var RECLAIM_RETRY_DELAY_MS = 40;
|
|
1726
|
+
var RECLAIM_TRIES = 8;
|
|
1669
1727
|
function matchSimpleGlob(pattern, name) {
|
|
1670
1728
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1671
1729
|
return new RegExp(`^${escaped}$`).test(name);
|
|
1672
1730
|
}
|
|
1673
1731
|
function readWorktreeInclude(repoPath) {
|
|
1674
|
-
const path =
|
|
1732
|
+
const path = join5(repoPath, ".worktreeinclude");
|
|
1675
1733
|
if (!existsSync3(path)) return [];
|
|
1676
|
-
return
|
|
1734
|
+
return readFileSync3(path, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
1677
1735
|
}
|
|
1678
1736
|
function resolveFilesToCopy(repoPath) {
|
|
1679
1737
|
const fromInclude = readWorktreeInclude(repoPath);
|
|
@@ -1712,10 +1770,10 @@ function copyConfiguredFiles(repoPath, worktreePath) {
|
|
|
1712
1770
|
const patterns = resolveFilesToCopy(repoPath);
|
|
1713
1771
|
const copied = [];
|
|
1714
1772
|
for (const rel of patterns) {
|
|
1715
|
-
const src =
|
|
1773
|
+
const src = join5(repoPath, rel);
|
|
1716
1774
|
if (!existsSync3(src)) continue;
|
|
1717
|
-
const dest =
|
|
1718
|
-
|
|
1775
|
+
const dest = join5(worktreePath, rel);
|
|
1776
|
+
mkdirSync4(dirname3(dest), { recursive: true });
|
|
1719
1777
|
copyFileSync(src, dest);
|
|
1720
1778
|
copied.push(rel);
|
|
1721
1779
|
}
|
|
@@ -1792,6 +1850,57 @@ function killableListenerPids(raw, selfPid = process.pid) {
|
|
|
1792
1850
|
}
|
|
1793
1851
|
return pids;
|
|
1794
1852
|
}
|
|
1853
|
+
function listUnixListenerPids(port, selfPid) {
|
|
1854
|
+
try {
|
|
1855
|
+
const raw = execFileSync("lsof", [`-tiTCP:${port}`, "-sTCP:LISTEN"], {
|
|
1856
|
+
encoding: "utf8",
|
|
1857
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1858
|
+
timeout: 2e3
|
|
1859
|
+
});
|
|
1860
|
+
return killableListenerPids(raw, selfPid);
|
|
1861
|
+
} catch {
|
|
1862
|
+
return [];
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
function listenerCwd(pid) {
|
|
1866
|
+
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
1867
|
+
try {
|
|
1868
|
+
const raw = execFileSync("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], {
|
|
1869
|
+
encoding: "utf8",
|
|
1870
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1871
|
+
timeout: 2e3
|
|
1872
|
+
});
|
|
1873
|
+
for (const line of raw.split("\n")) {
|
|
1874
|
+
if (line.startsWith("n") && line.length > 1) return line.slice(1);
|
|
1875
|
+
}
|
|
1876
|
+
} catch {
|
|
1877
|
+
}
|
|
1878
|
+
return null;
|
|
1879
|
+
}
|
|
1880
|
+
function realpathOrResolve(path) {
|
|
1881
|
+
try {
|
|
1882
|
+
return realpathSync(path);
|
|
1883
|
+
} catch {
|
|
1884
|
+
return resolve(path);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
function isOtherWorktreeListenerCwd(cwd, worktreePath) {
|
|
1888
|
+
const wt = realpathOrResolve(worktreePath);
|
|
1889
|
+
const c = realpathOrResolve(cwd);
|
|
1890
|
+
if (c === wt || c.startsWith(wt + sep)) return false;
|
|
1891
|
+
const parent = dirname3(wt);
|
|
1892
|
+
const thisName = basename(wt);
|
|
1893
|
+
if (!thisName || c === parent) return false;
|
|
1894
|
+
if (!c.startsWith(parent + sep)) return false;
|
|
1895
|
+
const first = c.slice(parent.length + 1).split(sep)[0];
|
|
1896
|
+
return Boolean(first) && first !== thisName;
|
|
1897
|
+
}
|
|
1898
|
+
function signalPid(pid, signal) {
|
|
1899
|
+
try {
|
|
1900
|
+
process.kill(pid, signal);
|
|
1901
|
+
} catch {
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1795
1904
|
function killListenersOnPorts(ports) {
|
|
1796
1905
|
const selfPid = process.pid;
|
|
1797
1906
|
for (const port of ports) {
|
|
@@ -1809,26 +1918,32 @@ function killListenersOnPorts(ports) {
|
|
|
1809
1918
|
);
|
|
1810
1919
|
continue;
|
|
1811
1920
|
}
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
raw = execFileSync("lsof", [`-tiTCP:${port}`, "-sTCP:LISTEN"], {
|
|
1815
|
-
encoding: "utf8",
|
|
1816
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
1817
|
-
timeout: 2e3
|
|
1818
|
-
});
|
|
1819
|
-
} catch {
|
|
1820
|
-
continue;
|
|
1821
|
-
}
|
|
1822
|
-
for (const pid of killableListenerPids(raw, selfPid)) {
|
|
1823
|
-
try {
|
|
1824
|
-
process.kill(pid, "SIGTERM");
|
|
1825
|
-
} catch {
|
|
1826
|
-
}
|
|
1921
|
+
for (const pid of listUnixListenerPids(port, selfPid)) {
|
|
1922
|
+
signalPid(pid, "SIGTERM");
|
|
1827
1923
|
}
|
|
1828
1924
|
} catch {
|
|
1829
1925
|
}
|
|
1830
1926
|
}
|
|
1831
1927
|
}
|
|
1928
|
+
function killStaleWorktreeListeners(ports, worktreePath, signal = "SIGTERM") {
|
|
1929
|
+
if (!worktreePath.trim()) {
|
|
1930
|
+
killListenersOnPorts(ports);
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1933
|
+
if (process.platform === "win32") {
|
|
1934
|
+
killListenersOnPorts(ports);
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
const selfPid = process.pid;
|
|
1938
|
+
for (const port of ports) {
|
|
1939
|
+
if (!Number.isFinite(port) || port <= 0) continue;
|
|
1940
|
+
for (const pid of listUnixListenerPids(port, selfPid)) {
|
|
1941
|
+
const cwd = listenerCwd(pid);
|
|
1942
|
+
if (cwd && isOtherWorktreeListenerCwd(cwd, worktreePath)) continue;
|
|
1943
|
+
signalPid(pid, signal);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1832
1947
|
function killScriptTree(child, ports = []) {
|
|
1833
1948
|
const pid = child.pid;
|
|
1834
1949
|
if (pid) {
|
|
@@ -1990,7 +2105,7 @@ function getRunMode(worktreePath, repoPath) {
|
|
|
1990
2105
|
return loadWorkspaceSettings(worktreePath, repoPath)?.runMode ?? "concurrent";
|
|
1991
2106
|
}
|
|
1992
2107
|
async function reservePort() {
|
|
1993
|
-
return new Promise((
|
|
2108
|
+
return new Promise((resolve2, reject) => {
|
|
1994
2109
|
const server = createServer();
|
|
1995
2110
|
let released = false;
|
|
1996
2111
|
const release = () => new Promise((res) => {
|
|
@@ -2007,7 +2122,7 @@ async function reservePort() {
|
|
|
2007
2122
|
void release().then(() => reject(new Error("Failed to allocate port")));
|
|
2008
2123
|
return;
|
|
2009
2124
|
}
|
|
2010
|
-
|
|
2125
|
+
resolve2({ port: addr.port, release });
|
|
2011
2126
|
});
|
|
2012
2127
|
server.on("error", reject);
|
|
2013
2128
|
});
|
|
@@ -2018,7 +2133,8 @@ async function allocatePort() {
|
|
|
2018
2133
|
return held.port;
|
|
2019
2134
|
}
|
|
2020
2135
|
async function tryReservePort(port) {
|
|
2021
|
-
|
|
2136
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) return null;
|
|
2137
|
+
return new Promise((resolve2) => {
|
|
2022
2138
|
const server = createServer();
|
|
2023
2139
|
let released = false;
|
|
2024
2140
|
const release = () => new Promise((res) => {
|
|
@@ -2029,24 +2145,54 @@ async function tryReservePort(port) {
|
|
|
2029
2145
|
released = true;
|
|
2030
2146
|
server.close(() => res());
|
|
2031
2147
|
});
|
|
2032
|
-
server.once("error", () =>
|
|
2148
|
+
server.once("error", () => {
|
|
2149
|
+
try {
|
|
2150
|
+
server.close();
|
|
2151
|
+
} catch {
|
|
2152
|
+
}
|
|
2153
|
+
resolve2(null);
|
|
2154
|
+
});
|
|
2033
2155
|
server.listen(port, "127.0.0.1", () => {
|
|
2034
|
-
|
|
2156
|
+
resolve2({ port, release });
|
|
2035
2157
|
});
|
|
2036
2158
|
});
|
|
2037
2159
|
}
|
|
2038
|
-
async function
|
|
2039
|
-
const held = [
|
|
2040
|
-
const
|
|
2160
|
+
async function tryReservePorts(ports) {
|
|
2161
|
+
const held = [];
|
|
2162
|
+
for (const port of ports) {
|
|
2163
|
+
const next = await tryReservePort(port);
|
|
2164
|
+
if (!next) {
|
|
2165
|
+
await Promise.all(held.map((h) => h.release()));
|
|
2166
|
+
return null;
|
|
2167
|
+
}
|
|
2168
|
+
held.push(next);
|
|
2169
|
+
}
|
|
2170
|
+
return held;
|
|
2171
|
+
}
|
|
2172
|
+
function normalizePreferredPorts(ports, size) {
|
|
2173
|
+
if (!ports?.length) return [];
|
|
2174
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2175
|
+
const out = [];
|
|
2176
|
+
for (const p of ports) {
|
|
2177
|
+
if (!Number.isInteger(p) || p <= 0 || p > 65535 || seen.has(p)) continue;
|
|
2178
|
+
seen.add(p);
|
|
2179
|
+
out.push(p);
|
|
2180
|
+
if (out.length >= size) break;
|
|
2181
|
+
}
|
|
2182
|
+
return out;
|
|
2183
|
+
}
|
|
2184
|
+
function delay(ms) {
|
|
2185
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2186
|
+
}
|
|
2187
|
+
async function fillPortRange(held, size) {
|
|
2188
|
+
if (held.length >= size) return held;
|
|
2189
|
+
const base = held[0]?.port;
|
|
2041
2190
|
try {
|
|
2042
|
-
for (let i =
|
|
2043
|
-
const candidate = base + i;
|
|
2044
|
-
const next = await tryReservePort(candidate);
|
|
2045
|
-
if (next)
|
|
2046
|
-
|
|
2047
|
-
} else {
|
|
2048
|
-
held.push(await reservePort());
|
|
2049
|
-
}
|
|
2191
|
+
for (let i = held.length; i < size; i++) {
|
|
2192
|
+
const candidate = base != null ? base + i : null;
|
|
2193
|
+
const next = candidate != null ? await tryReservePort(candidate) : null;
|
|
2194
|
+
if (next) held.push(next);
|
|
2195
|
+
else held.push(await reservePort());
|
|
2050
2196
|
}
|
|
2051
2197
|
return held;
|
|
2052
2198
|
} catch (err) {
|
|
@@ -2054,6 +2200,33 @@ async function reservePortRange(size = PORT_RANGE_SIZE) {
|
|
|
2054
2200
|
throw err;
|
|
2055
2201
|
}
|
|
2056
2202
|
}
|
|
2203
|
+
async function reclaimPreferredPorts(ports, worktreePath) {
|
|
2204
|
+
let held = await tryReservePorts(ports);
|
|
2205
|
+
if (held) return held;
|
|
2206
|
+
if (worktreePath) {
|
|
2207
|
+
killStaleWorktreeListeners(ports, worktreePath, "SIGTERM");
|
|
2208
|
+
} else {
|
|
2209
|
+
killListenersOnPorts(ports);
|
|
2210
|
+
}
|
|
2211
|
+
for (let i = 0; i < RECLAIM_TRIES; i++) {
|
|
2212
|
+
await delay(RECLAIM_RETRY_DELAY_MS);
|
|
2213
|
+
held = await tryReservePorts(ports);
|
|
2214
|
+
if (held) return held;
|
|
2215
|
+
if (i === 2 && worktreePath) {
|
|
2216
|
+
killStaleWorktreeListeners(ports, worktreePath, "SIGKILL");
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
return null;
|
|
2220
|
+
}
|
|
2221
|
+
async function reservePortRange(size = PORT_RANGE_SIZE, opts) {
|
|
2222
|
+
const preferred = normalizePreferredPorts(opts?.preferred, size);
|
|
2223
|
+
if (preferred.length) {
|
|
2224
|
+
const reused = await reclaimPreferredPorts(preferred, opts?.worktreePath);
|
|
2225
|
+
if (reused) return fillPortRange(reused, size);
|
|
2226
|
+
}
|
|
2227
|
+
const held = [await reservePort()];
|
|
2228
|
+
return fillPortRange(held, size);
|
|
2229
|
+
}
|
|
2057
2230
|
async function allocatePortRange(size = PORT_RANGE_SIZE) {
|
|
2058
2231
|
const held = await reservePortRange(size);
|
|
2059
2232
|
const ports = held.map((h) => h.port);
|
|
@@ -2063,8 +2236,13 @@ async function allocatePortRange(size = PORT_RANGE_SIZE) {
|
|
|
2063
2236
|
async function startDevServer(repoPath, worktreePath, onLine, opts) {
|
|
2064
2237
|
const script = getRunScript(worktreePath, repoPath, opts?.scriptName);
|
|
2065
2238
|
if (!script) return null;
|
|
2066
|
-
const
|
|
2239
|
+
const preferred = loadWorktreeRunPorts(worktreePath, script.name) ?? opts?.preferredPorts;
|
|
2240
|
+
const held = await reservePortRange(PORT_RANGE_SIZE, {
|
|
2241
|
+
preferred: preferred ?? void 0,
|
|
2242
|
+
worktreePath
|
|
2243
|
+
});
|
|
2067
2244
|
const ports = held.map((h) => h.port);
|
|
2245
|
+
saveWorktreeRunPorts(worktreePath, script.name, ports);
|
|
2068
2246
|
try {
|
|
2069
2247
|
const handle = await spawnWorkspaceScript(script.command, {
|
|
2070
2248
|
worktreePath,
|
|
@@ -2090,8 +2268,8 @@ async function startDevServer(repoPath, worktreePath, onLine, opts) {
|
|
|
2090
2268
|
|
|
2091
2269
|
// src/store/setup-log.ts
|
|
2092
2270
|
import { createHash } from "crypto";
|
|
2093
|
-
import { existsSync as existsSync4, readFileSync as
|
|
2094
|
-
import { join as
|
|
2271
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
2272
|
+
import { join as join6 } from "path";
|
|
2095
2273
|
var EMPTY = {
|
|
2096
2274
|
output: "",
|
|
2097
2275
|
running: false,
|
|
@@ -2133,7 +2311,7 @@ function materialize(mem) {
|
|
|
2133
2311
|
};
|
|
2134
2312
|
}
|
|
2135
2313
|
function setupLogPath(threadId) {
|
|
2136
|
-
return
|
|
2314
|
+
return join6(threadsDir(), `${threadId}.setup.log.json`);
|
|
2137
2315
|
}
|
|
2138
2316
|
function setupLogKeyForWorktree(worktreePath) {
|
|
2139
2317
|
const norm = normalizeWorktreePath(worktreePath);
|
|
@@ -2160,7 +2338,7 @@ function persistNow(threadId) {
|
|
|
2160
2338
|
const mem = memory.get(threadId);
|
|
2161
2339
|
if (!mem) return;
|
|
2162
2340
|
try {
|
|
2163
|
-
|
|
2341
|
+
writeFileSync3(
|
|
2164
2342
|
setupLogPath(threadId),
|
|
2165
2343
|
`${JSON.stringify(materialize(mem))}
|
|
2166
2344
|
`,
|
|
@@ -2183,7 +2361,7 @@ function loadFromDisk(threadId) {
|
|
|
2183
2361
|
const path = setupLogPath(threadId);
|
|
2184
2362
|
if (!existsSync4(path)) return null;
|
|
2185
2363
|
try {
|
|
2186
|
-
const raw = JSON.parse(
|
|
2364
|
+
const raw = JSON.parse(readFileSync4(path, "utf8"));
|
|
2187
2365
|
return {
|
|
2188
2366
|
output: typeof raw.output === "string" ? raw.output : "",
|
|
2189
2367
|
running: Boolean(raw.running),
|
|
@@ -2242,8 +2420,8 @@ function finishSetupLog(threadId, exitCode, source) {
|
|
|
2242
2420
|
|
|
2243
2421
|
// src/store/run-log.ts
|
|
2244
2422
|
import { createHash as createHash2 } from "crypto";
|
|
2245
|
-
import { existsSync as existsSync5, readFileSync as
|
|
2246
|
-
import { join as
|
|
2423
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
2424
|
+
import { join as join7 } from "path";
|
|
2247
2425
|
var EMPTY2 = {
|
|
2248
2426
|
output: "",
|
|
2249
2427
|
running: false,
|
|
@@ -2282,7 +2460,7 @@ function safeScriptToken(scriptName) {
|
|
|
2282
2460
|
return token || "dev";
|
|
2283
2461
|
}
|
|
2284
2462
|
function runLogPath(key) {
|
|
2285
|
-
return
|
|
2463
|
+
return join7(threadsDir(), `${key}.run.log.json`);
|
|
2286
2464
|
}
|
|
2287
2465
|
function runLogKeyForWorktree(worktreePath, scriptName) {
|
|
2288
2466
|
const norm = normalizeWorktreePath(worktreePath);
|
|
@@ -2311,7 +2489,7 @@ function persistNow2(key) {
|
|
|
2311
2489
|
const mem = memory2.get(key);
|
|
2312
2490
|
if (!mem) return;
|
|
2313
2491
|
try {
|
|
2314
|
-
|
|
2492
|
+
writeFileSync4(runLogPath(key), `${JSON.stringify(materialize2(mem))}
|
|
2315
2493
|
`, "utf8");
|
|
2316
2494
|
} catch {
|
|
2317
2495
|
}
|
|
@@ -2330,7 +2508,7 @@ function loadFromDisk2(key) {
|
|
|
2330
2508
|
const path = runLogPath(key);
|
|
2331
2509
|
if (!existsSync5(path)) return null;
|
|
2332
2510
|
try {
|
|
2333
|
-
const raw = JSON.parse(
|
|
2511
|
+
const raw = JSON.parse(readFileSync5(path, "utf8"));
|
|
2334
2512
|
return {
|
|
2335
2513
|
output: typeof raw.output === "string" ? raw.output : "",
|
|
2336
2514
|
running: Boolean(raw.running),
|
|
@@ -2391,7 +2569,7 @@ function flushRunLog(key) {
|
|
|
2391
2569
|
|
|
2392
2570
|
// src/git/orphan-cleanup.ts
|
|
2393
2571
|
import { existsSync as existsSync6, readdirSync as readdirSync2, statSync } from "fs";
|
|
2394
|
-
import { join as
|
|
2572
|
+
import { join as join8 } from "path";
|
|
2395
2573
|
function isSideboardWorktreePath(path) {
|
|
2396
2574
|
return path.includes("/.sideboard/worktrees/") || path.includes("/sideboard/workspaces/");
|
|
2397
2575
|
}
|
|
@@ -2438,9 +2616,9 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
2438
2616
|
if (existsSync6(root)) {
|
|
2439
2617
|
for (const entry of readdirSync2(root, { withFileTypes: true })) {
|
|
2440
2618
|
if (!entry.isDirectory()) continue;
|
|
2441
|
-
const path =
|
|
2619
|
+
const path = join8(root, entry.name).replace(/\/$/, "");
|
|
2442
2620
|
if (known.has(path) || seen.has(path)) continue;
|
|
2443
|
-
if (!existsSync6(
|
|
2621
|
+
if (!existsSync6(join8(path, ".git"))) continue;
|
|
2444
2622
|
seen.add(path);
|
|
2445
2623
|
let mtimeMs = 0;
|
|
2446
2624
|
try {
|
|
@@ -2654,7 +2832,7 @@ async function applyThreadIntoMain(thread, opts) {
|
|
|
2654
2832
|
|
|
2655
2833
|
// src/git/clone-repo.ts
|
|
2656
2834
|
import { existsSync as existsSync7 } from "fs";
|
|
2657
|
-
import { basename as basename2, join as
|
|
2835
|
+
import { basename as basename2, join as join9 } from "path";
|
|
2658
2836
|
import { execa as execa4 } from "execa";
|
|
2659
2837
|
async function cloneRepoIntoSideboard(opts) {
|
|
2660
2838
|
const url = opts.url.trim();
|
|
@@ -2665,7 +2843,7 @@ async function cloneRepoIntoSideboard(opts) {
|
|
|
2665
2843
|
name = leaf || "repo";
|
|
2666
2844
|
}
|
|
2667
2845
|
name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
|
|
2668
|
-
const dest =
|
|
2846
|
+
const dest = join9(sideboardReposDir(), name);
|
|
2669
2847
|
if (existsSync7(dest)) {
|
|
2670
2848
|
const repoPath2 = await resolveRepoRoot(dest);
|
|
2671
2849
|
const workspace2 = await ensureWorkspace(repoPath2);
|
|
@@ -2691,13 +2869,13 @@ function isAdoptableRunScriptRequest(req) {
|
|
|
2691
2869
|
}
|
|
2692
2870
|
|
|
2693
2871
|
// src/store/desktop-host.ts
|
|
2694
|
-
import { readFileSync as
|
|
2695
|
-
import { join as
|
|
2872
|
+
import { readFileSync as readFileSync6, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
|
|
2873
|
+
import { join as join10 } from "path";
|
|
2696
2874
|
function desktopHostPidPath() {
|
|
2697
|
-
return
|
|
2875
|
+
return join10(appDataDir(), "desktop-host.pid");
|
|
2698
2876
|
}
|
|
2699
2877
|
function claimDesktopHost(pid = process.pid) {
|
|
2700
|
-
|
|
2878
|
+
writeFileSync5(desktopHostPidPath(), `${pid}
|
|
2701
2879
|
`, "utf8");
|
|
2702
2880
|
}
|
|
2703
2881
|
function releaseDesktopHost(pid = process.pid) {
|
|
@@ -2709,7 +2887,7 @@ function releaseDesktopHost(pid = process.pid) {
|
|
|
2709
2887
|
}
|
|
2710
2888
|
function readDesktopHostPid() {
|
|
2711
2889
|
try {
|
|
2712
|
-
const pid = Number.parseInt(
|
|
2890
|
+
const pid = Number.parseInt(readFileSync6(desktopHostPidPath(), "utf8").trim(), 10);
|
|
2713
2891
|
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
2714
2892
|
return pid;
|
|
2715
2893
|
} catch {
|
|
@@ -2782,12 +2960,12 @@ function notifyParentOfChildHalt(child, status, send) {
|
|
|
2782
2960
|
import {
|
|
2783
2961
|
appendFileSync as appendFileSync2,
|
|
2784
2962
|
existsSync as existsSync8,
|
|
2785
|
-
mkdirSync as
|
|
2963
|
+
mkdirSync as mkdirSync5,
|
|
2786
2964
|
readdirSync as readdirSync3,
|
|
2787
|
-
readFileSync as
|
|
2788
|
-
writeFileSync as
|
|
2965
|
+
readFileSync as readFileSync7,
|
|
2966
|
+
writeFileSync as writeFileSync6
|
|
2789
2967
|
} from "fs";
|
|
2790
|
-
import { dirname as
|
|
2968
|
+
import { dirname as dirname4, join as join11 } from "path";
|
|
2791
2969
|
|
|
2792
2970
|
// src/mcp/wait-for-turn.ts
|
|
2793
2971
|
var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
@@ -2863,11 +3041,11 @@ function jobTreeAlive(pid) {
|
|
|
2863
3041
|
}
|
|
2864
3042
|
function readIntFile(file) {
|
|
2865
3043
|
if (!existsSync8(file)) return null;
|
|
2866
|
-
const n = Number.parseInt(
|
|
3044
|
+
const n = Number.parseInt(readFileSync7(file, "utf8").trim(), 10);
|
|
2867
3045
|
return Number.isInteger(n) ? n : null;
|
|
2868
3046
|
}
|
|
2869
3047
|
function jobDir(root, id, legacy = false) {
|
|
2870
|
-
return
|
|
3048
|
+
return join11(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
|
|
2871
3049
|
}
|
|
2872
3050
|
function resolveJobDir(root, id) {
|
|
2873
3051
|
const modern = jobDir(root, id, false);
|
|
@@ -2877,7 +3055,7 @@ function resolveJobDir(root, id) {
|
|
|
2877
3055
|
return modern;
|
|
2878
3056
|
}
|
|
2879
3057
|
function listJobIdsIn(root, rel) {
|
|
2880
|
-
const dir =
|
|
3058
|
+
const dir = join11(root, rel);
|
|
2881
3059
|
if (!existsSync8(dir)) return [];
|
|
2882
3060
|
return readdirSync3(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
|
|
2883
3061
|
}
|
|
@@ -2891,7 +3069,7 @@ function listRunningDetachedJobs(worktreePath) {
|
|
|
2891
3069
|
const running = [];
|
|
2892
3070
|
for (const id of ids) {
|
|
2893
3071
|
const dir = resolveJobDir(root, id);
|
|
2894
|
-
const pid = readIntFile(
|
|
3072
|
+
const pid = readIntFile(join11(dir, "pid"));
|
|
2895
3073
|
if (jobTreeAlive(pid)) running.push(id);
|
|
2896
3074
|
}
|
|
2897
3075
|
return running.sort();
|
|
@@ -2975,7 +3153,7 @@ function planJobContinue(opts) {
|
|
|
2975
3153
|
}
|
|
2976
3154
|
function tailProgress(logFile, maxLines = 12) {
|
|
2977
3155
|
if (!existsSync8(logFile)) return "(no log yet)";
|
|
2978
|
-
const lines =
|
|
3156
|
+
const lines = readFileSync7(logFile, "utf8").split("\n");
|
|
2979
3157
|
return lines.slice(-maxLines).join("\n");
|
|
2980
3158
|
}
|
|
2981
3159
|
function lastProgressLine(progress) {
|
|
@@ -2984,7 +3162,7 @@ function lastProgressLine(progress) {
|
|
|
2984
3162
|
}
|
|
2985
3163
|
function readLogLines(file) {
|
|
2986
3164
|
if (!existsSync8(file)) return [];
|
|
2987
|
-
const lines =
|
|
3165
|
+
const lines = readFileSync7(file, "utf8").split("\n");
|
|
2988
3166
|
if (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
2989
3167
|
return lines;
|
|
2990
3168
|
}
|
|
@@ -2995,15 +3173,15 @@ function takeDelta(logFile, cursorFile) {
|
|
|
2995
3173
|
return { delta: capJobLogDelta(lines.slice(start).join("\n")), nextCursor: lines.length };
|
|
2996
3174
|
}
|
|
2997
3175
|
function snapshotJob(dir) {
|
|
2998
|
-
const pid = readIntFile(
|
|
3176
|
+
const pid = readIntFile(join11(dir, "pid"));
|
|
2999
3177
|
const running = jobTreeAlive(pid);
|
|
3000
3178
|
return {
|
|
3001
3179
|
pid,
|
|
3002
3180
|
running,
|
|
3003
|
-
exitCode: readIntFile(
|
|
3004
|
-
log:
|
|
3005
|
-
cursor:
|
|
3006
|
-
progress: tailProgress(
|
|
3181
|
+
exitCode: readIntFile(join11(dir, "exit")),
|
|
3182
|
+
log: join11(dir, "log"),
|
|
3183
|
+
cursor: join11(dir, "present.cursor"),
|
|
3184
|
+
progress: tailProgress(join11(dir, "log"))
|
|
3007
3185
|
};
|
|
3008
3186
|
}
|
|
3009
3187
|
function toResult(id, snap, extra) {
|
|
@@ -3012,8 +3190,8 @@ function toResult(id, snap, extra) {
|
|
|
3012
3190
|
const stillRunning = snap.running && !ok;
|
|
3013
3191
|
const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
|
|
3014
3192
|
try {
|
|
3015
|
-
|
|
3016
|
-
|
|
3193
|
+
mkdirSync5(dirname4(snap.cursor), { recursive: true });
|
|
3194
|
+
writeFileSync6(snap.cursor, `${nextCursor}
|
|
3017
3195
|
`);
|
|
3018
3196
|
} catch {
|
|
3019
3197
|
}
|
|
@@ -3034,7 +3212,7 @@ function toResult(id, snap, extra) {
|
|
|
3034
3212
|
};
|
|
3035
3213
|
}
|
|
3036
3214
|
async function sleepMs(ms) {
|
|
3037
|
-
await new Promise((
|
|
3215
|
+
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
3038
3216
|
}
|
|
3039
3217
|
async function waitForDetachedJob(cwd, id, opts) {
|
|
3040
3218
|
const jobId = sanitizeDetachedJobId(id);
|
|
@@ -3088,7 +3266,7 @@ function killProcessTree(pid, signal) {
|
|
|
3088
3266
|
}
|
|
3089
3267
|
function appendJobLog(logFile, line) {
|
|
3090
3268
|
try {
|
|
3091
|
-
|
|
3269
|
+
mkdirSync5(dirname4(logFile), { recursive: true });
|
|
3092
3270
|
const body = line.endsWith("\n") ? line : `${line}
|
|
3093
3271
|
`;
|
|
3094
3272
|
appendFileSync2(logFile, body);
|
|
@@ -3137,10 +3315,10 @@ async function stopDetachedJob(cwd, id, opts) {
|
|
|
3137
3315
|
killProcessTree(targetPid, "SIGKILL");
|
|
3138
3316
|
await sleep(100);
|
|
3139
3317
|
}
|
|
3140
|
-
const exitFile =
|
|
3318
|
+
const exitFile = join11(dir, "exit");
|
|
3141
3319
|
if (!existsSync8(exitFile)) {
|
|
3142
3320
|
try {
|
|
3143
|
-
|
|
3321
|
+
writeFileSync6(exitFile, "1\n");
|
|
3144
3322
|
} catch {
|
|
3145
3323
|
}
|
|
3146
3324
|
}
|
|
@@ -4027,8 +4205,8 @@ import {
|
|
|
4027
4205
|
existsSync as existsSync10,
|
|
4028
4206
|
renameSync,
|
|
4029
4207
|
unlinkSync as unlinkSync2,
|
|
4030
|
-
writeFileSync as
|
|
4031
|
-
readFileSync as
|
|
4208
|
+
writeFileSync as writeFileSync7,
|
|
4209
|
+
readFileSync as readFileSync8
|
|
4032
4210
|
} from "fs";
|
|
4033
4211
|
var buffers = /* @__PURE__ */ new Map();
|
|
4034
4212
|
var FLUSH_MS = 800;
|
|
@@ -4105,7 +4283,7 @@ function writeTurnLive(threadId, progress) {
|
|
|
4105
4283
|
const path = threadLivePath(threadId);
|
|
4106
4284
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
4107
4285
|
try {
|
|
4108
|
-
|
|
4286
|
+
writeFileSync7(tmp, JSON.stringify(progress), "utf8");
|
|
4109
4287
|
renameSync(tmp, path);
|
|
4110
4288
|
} catch {
|
|
4111
4289
|
try {
|
|
@@ -4118,7 +4296,7 @@ function readTurnLive(threadId) {
|
|
|
4118
4296
|
const path = threadLivePath(threadId);
|
|
4119
4297
|
if (!existsSync10(path)) return null;
|
|
4120
4298
|
try {
|
|
4121
|
-
const raw = JSON.parse(
|
|
4299
|
+
const raw = JSON.parse(readFileSync8(path, "utf8"));
|
|
4122
4300
|
if (!raw || typeof raw.summary !== "string") return null;
|
|
4123
4301
|
return raw;
|
|
4124
4302
|
} catch {
|
|
@@ -4345,23 +4523,23 @@ import {
|
|
|
4345
4523
|
existsSync as existsSync11,
|
|
4346
4524
|
mkdtempSync,
|
|
4347
4525
|
readdirSync as readdirSync4,
|
|
4348
|
-
readFileSync as
|
|
4526
|
+
readFileSync as readFileSync9,
|
|
4349
4527
|
rmSync
|
|
4350
4528
|
} from "fs";
|
|
4351
4529
|
import { tmpdir } from "os";
|
|
4352
|
-
import { join as
|
|
4530
|
+
import { join as join12 } from "path";
|
|
4353
4531
|
import { createRequire } from "module";
|
|
4354
|
-
var CONDUCTOR_APP_SUPPORT =
|
|
4532
|
+
var CONDUCTOR_APP_SUPPORT = join12(
|
|
4355
4533
|
process.env.HOME ?? "",
|
|
4356
4534
|
"Library",
|
|
4357
4535
|
"Application Support",
|
|
4358
4536
|
"com.conductor.app"
|
|
4359
4537
|
);
|
|
4360
|
-
var CONDUCTOR_DB =
|
|
4361
|
-
var CURSOR_SDK_STORE =
|
|
4538
|
+
var CONDUCTOR_DB = join12(CONDUCTOR_APP_SUPPORT, "conductor.db");
|
|
4539
|
+
var CURSOR_SDK_STORE = join12(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
|
|
4362
4540
|
function thisModuleFile() {
|
|
4363
4541
|
const cjsFile = typeof __filename !== "undefined" ? __filename : "";
|
|
4364
|
-
return cjsFile || process.argv[1] ||
|
|
4542
|
+
return cjsFile || process.argv[1] || join12(process.cwd(), "package.json");
|
|
4365
4543
|
}
|
|
4366
4544
|
function openReadonlySqlite(file) {
|
|
4367
4545
|
const req = createRequire(thisModuleFile());
|
|
@@ -4389,11 +4567,11 @@ function resolveConductorCursorAgentId(workspacePath) {
|
|
|
4389
4567
|
return null;
|
|
4390
4568
|
}
|
|
4391
4569
|
for (const hash of hashes) {
|
|
4392
|
-
const agentsFile =
|
|
4570
|
+
const agentsFile = join12(CURSOR_SDK_STORE, hash, "agents.ndjson");
|
|
4393
4571
|
if (!existsSync11(agentsFile)) continue;
|
|
4394
4572
|
let text;
|
|
4395
4573
|
try {
|
|
4396
|
-
text =
|
|
4574
|
+
text = readFileSync9(agentsFile, "utf8");
|
|
4397
4575
|
} catch {
|
|
4398
4576
|
continue;
|
|
4399
4577
|
}
|
|
@@ -4449,8 +4627,8 @@ function listConductorWorkspaces() {
|
|
|
4449
4627
|
if (!existsSync11(CONDUCTOR_DB)) {
|
|
4450
4628
|
throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
|
|
4451
4629
|
}
|
|
4452
|
-
const tmp = mkdtempSync(
|
|
4453
|
-
const snapshot =
|
|
4630
|
+
const tmp = mkdtempSync(join12(tmpdir(), "sideboard-conductor-"));
|
|
4631
|
+
const snapshot = join12(tmp, "conductor.db");
|
|
4454
4632
|
try {
|
|
4455
4633
|
copyFileSync2(CONDUCTOR_DB, snapshot);
|
|
4456
4634
|
for (const suffix of ["-wal", "-shm"]) {
|
|
@@ -4540,8 +4718,8 @@ function importConductorWorkspace(workspaceId) {
|
|
|
4540
4718
|
if (!existsSync11(CONDUCTOR_DB)) {
|
|
4541
4719
|
throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
|
|
4542
4720
|
}
|
|
4543
|
-
const tmp = mkdtempSync(
|
|
4544
|
-
const snapshot =
|
|
4721
|
+
const tmp = mkdtempSync(join12(tmpdir(), "sideboard-conductor-"));
|
|
4722
|
+
const snapshot = join12(tmp, "conductor.db");
|
|
4545
4723
|
try {
|
|
4546
4724
|
copyFileSync2(CONDUCTOR_DB, snapshot);
|
|
4547
4725
|
for (const suffix of ["-wal", "-shm"]) {
|
|
@@ -4931,8 +5109,8 @@ function stackAgentDefaultsFrom(input) {
|
|
|
4931
5109
|
}
|
|
4932
5110
|
|
|
4933
5111
|
// src/diff/diff.ts
|
|
4934
|
-
import { existsSync as existsSync13, mkdirSync as
|
|
4935
|
-
import { dirname as
|
|
5112
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
|
|
5113
|
+
import { dirname as dirname5, join as join13 } from "path";
|
|
4936
5114
|
async function inspectGitWorktree(worktreePath) {
|
|
4937
5115
|
if (!worktreePath || !existsSync13(worktreePath)) return "missing_worktree";
|
|
4938
5116
|
const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
|
|
@@ -5078,11 +5256,11 @@ new file mode 100644
|
|
|
5078
5256
|
};
|
|
5079
5257
|
}
|
|
5080
5258
|
async function untrackedPatch(worktreePath, path, maxHunk) {
|
|
5081
|
-
const abs =
|
|
5259
|
+
const abs = join13(worktreePath, path);
|
|
5082
5260
|
try {
|
|
5083
5261
|
const st = statSync2(abs);
|
|
5084
5262
|
if (st.isFile() && st.size > maxHunk) {
|
|
5085
|
-
const buf =
|
|
5263
|
+
const buf = readFileSync10(abs).subarray(0, maxHunk);
|
|
5086
5264
|
return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
|
|
5087
5265
|
}
|
|
5088
5266
|
} catch {
|
|
@@ -5550,7 +5728,7 @@ async function getDiff(worktreePath, repoPath, opts) {
|
|
|
5550
5728
|
}
|
|
5551
5729
|
function statWorktreePath(worktreePath, relativePath) {
|
|
5552
5730
|
assertSafeRelativePath(relativePath);
|
|
5553
|
-
const abs =
|
|
5731
|
+
const abs = join13(worktreePath, relativePath);
|
|
5554
5732
|
if (!existsSync13(abs)) return "missing";
|
|
5555
5733
|
const st = statSync2(abs);
|
|
5556
5734
|
if (st.isDirectory()) return "dir";
|
|
@@ -5592,7 +5770,7 @@ var DEFAULT_UPLOAD_MAX_BYTES = 5e7;
|
|
|
5592
5770
|
function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
5593
5771
|
assertSafeRelativePath(relativePath);
|
|
5594
5772
|
const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
|
|
5595
|
-
const abs =
|
|
5773
|
+
const abs = join13(worktreePath, relativePath);
|
|
5596
5774
|
const st = statSync2(abs);
|
|
5597
5775
|
if (!st.isFile()) {
|
|
5598
5776
|
throw new Error(`Not a file: ${relativePath}`);
|
|
@@ -5602,7 +5780,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
|
5602
5780
|
`File too large to upload (${st.size} bytes; max ${maxBytes})`
|
|
5603
5781
|
);
|
|
5604
5782
|
}
|
|
5605
|
-
const buf =
|
|
5783
|
+
const buf = readFileSync10(abs);
|
|
5606
5784
|
return {
|
|
5607
5785
|
path: relativePath,
|
|
5608
5786
|
contentBase64: buf.toString("base64"),
|
|
@@ -5612,12 +5790,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
|
5612
5790
|
function readWorktreeFile(worktreePath, relativePath, opts) {
|
|
5613
5791
|
assertSafeRelativePath(relativePath);
|
|
5614
5792
|
const maxBytes = opts?.maxBytes ?? 2e5;
|
|
5615
|
-
const abs =
|
|
5793
|
+
const abs = join13(worktreePath, relativePath);
|
|
5616
5794
|
const st = statSync2(abs);
|
|
5617
5795
|
if (!st.isFile()) {
|
|
5618
5796
|
throw new Error(`Not a file: ${relativePath}`);
|
|
5619
5797
|
}
|
|
5620
|
-
const buf =
|
|
5798
|
+
const buf = readFileSync10(abs);
|
|
5621
5799
|
if (isImageRelativePath(relativePath)) {
|
|
5622
5800
|
const maxImageBytes = Math.max(maxBytes, 15e6);
|
|
5623
5801
|
const truncated2 = buf.length > maxImageBytes;
|
|
@@ -5660,9 +5838,9 @@ function assertSafeRelativePath(relativePath) {
|
|
|
5660
5838
|
}
|
|
5661
5839
|
function writeWorktreeFile(worktreePath, relativePath, content) {
|
|
5662
5840
|
assertSafeRelativePath(relativePath);
|
|
5663
|
-
const abs =
|
|
5664
|
-
|
|
5665
|
-
|
|
5841
|
+
const abs = join13(worktreePath, relativePath);
|
|
5842
|
+
mkdirSync6(dirname5(abs), { recursive: true });
|
|
5843
|
+
writeFileSync8(abs, content, "utf8");
|
|
5666
5844
|
return { path: relativePath };
|
|
5667
5845
|
}
|
|
5668
5846
|
async function getWorktreeDirtyStat(worktreePath) {
|
|
@@ -5884,9 +6062,9 @@ async function confirmLand(thread, opts) {
|
|
|
5884
6062
|
}
|
|
5885
6063
|
|
|
5886
6064
|
// src/skills/discover.ts
|
|
5887
|
-
import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as
|
|
6065
|
+
import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
|
|
5888
6066
|
import { homedir } from "os";
|
|
5889
|
-
import { join as
|
|
6067
|
+
import { join as join14 } from "path";
|
|
5890
6068
|
|
|
5891
6069
|
// src/skills/bundled/long-running.ts
|
|
5892
6070
|
var LONG_RUNNING_SKILL_COMMAND = "long-running";
|
|
@@ -6023,7 +6201,7 @@ function parseFrontmatter(content) {
|
|
|
6023
6201
|
}
|
|
6024
6202
|
function readSkill(skillMd, source) {
|
|
6025
6203
|
try {
|
|
6026
|
-
const content =
|
|
6204
|
+
const content = readFileSync11(skillMd, "utf8");
|
|
6027
6205
|
const { name: fmName, description } = parseFrontmatter(content);
|
|
6028
6206
|
const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
|
|
6029
6207
|
const name = fmName || dirName;
|
|
@@ -6051,7 +6229,7 @@ function scanSkillsDir(dir, source, out) {
|
|
|
6051
6229
|
}
|
|
6052
6230
|
for (const entry of entries) {
|
|
6053
6231
|
if (entry.startsWith(".")) continue;
|
|
6054
|
-
const skillMd =
|
|
6232
|
+
const skillMd = join14(dir, entry, "SKILL.md");
|
|
6055
6233
|
if (!existsSync14(skillMd)) continue;
|
|
6056
6234
|
try {
|
|
6057
6235
|
if (!statSync3(skillMd).isFile()) continue;
|
|
@@ -6073,12 +6251,12 @@ function scanClaudePluginSkills(pluginsRoot, out) {
|
|
|
6073
6251
|
return;
|
|
6074
6252
|
}
|
|
6075
6253
|
if (lookingForSkillsDir && entries.includes("SKILL.md")) {
|
|
6076
|
-
const skill = readSkill(
|
|
6254
|
+
const skill = readSkill(join14(dir, "SKILL.md"), "cli");
|
|
6077
6255
|
if (skill) out.push(skill);
|
|
6078
6256
|
}
|
|
6079
6257
|
for (const entry of entries) {
|
|
6080
6258
|
if (entry === "node_modules" || entry === ".git") continue;
|
|
6081
|
-
const full =
|
|
6259
|
+
const full = join14(dir, entry);
|
|
6082
6260
|
try {
|
|
6083
6261
|
if (!statSync3(full).isDirectory()) continue;
|
|
6084
6262
|
} catch {
|
|
@@ -6098,17 +6276,17 @@ function discoverSkills(worktreePath) {
|
|
|
6098
6276
|
const home = homedir();
|
|
6099
6277
|
const collected = [];
|
|
6100
6278
|
for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
|
|
6101
|
-
scanSkillsDir(
|
|
6279
|
+
scanSkillsDir(join14(worktreePath, rel), "workspace", collected);
|
|
6102
6280
|
}
|
|
6103
6281
|
for (const abs of [
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6282
|
+
join14(home, ".claude/skills"),
|
|
6283
|
+
join14(home, ".cursor/skills"),
|
|
6284
|
+
join14(home, ".sideboard/skills"),
|
|
6285
|
+
join14(home, ".brightsy/skills")
|
|
6108
6286
|
]) {
|
|
6109
6287
|
scanSkillsDir(abs, "user", collected);
|
|
6110
6288
|
}
|
|
6111
|
-
scanClaudePluginSkills(
|
|
6289
|
+
scanClaudePluginSkills(join14(home, ".claude/plugins"), collected);
|
|
6112
6290
|
collected.push(...bundledSkills());
|
|
6113
6291
|
const rank = {
|
|
6114
6292
|
workspace: 0,
|
|
@@ -6134,7 +6312,7 @@ function readSkillBody(skillPath, maxChars = 12e3) {
|
|
|
6134
6312
|
\u2026(truncated)` : body;
|
|
6135
6313
|
}
|
|
6136
6314
|
}
|
|
6137
|
-
const raw =
|
|
6315
|
+
const raw = readFileSync11(skillPath, "utf8");
|
|
6138
6316
|
if (raw.startsWith("---")) {
|
|
6139
6317
|
const end = raw.indexOf("\n---", 3);
|
|
6140
6318
|
if (end >= 0) {
|
|
@@ -6280,11 +6458,11 @@ function shiftQueuedItem(queue, queueAttachments) {
|
|
|
6280
6458
|
|
|
6281
6459
|
// src/agents/instructions.ts
|
|
6282
6460
|
import { existsSync as existsSync16 } from "fs";
|
|
6283
|
-
import { join as
|
|
6461
|
+
import { join as join16 } from "path";
|
|
6284
6462
|
|
|
6285
6463
|
// src/skills/detached-job-path.ts
|
|
6286
6464
|
import { existsSync as existsSync15 } from "fs";
|
|
6287
|
-
import { dirname as
|
|
6465
|
+
import { dirname as dirname6, join as join15 } from "path";
|
|
6288
6466
|
import { fileURLToPath } from "url";
|
|
6289
6467
|
var DETACHED_JOB_FILENAMES = ["detached-job.cjs", "detached-job.js"];
|
|
6290
6468
|
function compiledModuleDir() {
|
|
@@ -6293,7 +6471,7 @@ function compiledModuleDir() {
|
|
|
6293
6471
|
try {
|
|
6294
6472
|
const url = import.meta.url;
|
|
6295
6473
|
if (typeof url === "string" && url.length > 0) {
|
|
6296
|
-
return
|
|
6474
|
+
return dirname6(fileURLToPath(url));
|
|
6297
6475
|
}
|
|
6298
6476
|
} catch {
|
|
6299
6477
|
}
|
|
@@ -6301,7 +6479,7 @@ function compiledModuleDir() {
|
|
|
6301
6479
|
}
|
|
6302
6480
|
function detachedJobScriptIn(dir) {
|
|
6303
6481
|
for (const name of DETACHED_JOB_FILENAMES) {
|
|
6304
|
-
const script =
|
|
6482
|
+
const script = join15(dir, "scripts", name);
|
|
6305
6483
|
if (existsSync15(script)) return script;
|
|
6306
6484
|
}
|
|
6307
6485
|
return null;
|
|
@@ -6319,7 +6497,7 @@ function resolveDetachedJobScript() {
|
|
|
6319
6497
|
for (let i = 0; i < 8; i++) {
|
|
6320
6498
|
const candidate = detachedJobScriptIn(dir);
|
|
6321
6499
|
if (candidate) return candidate;
|
|
6322
|
-
const parent =
|
|
6500
|
+
const parent = dirname6(dir);
|
|
6323
6501
|
if (parent === dir) break;
|
|
6324
6502
|
dir = parent;
|
|
6325
6503
|
}
|
|
@@ -6424,9 +6602,9 @@ function formatWorktreeDirective(thread, opts) {
|
|
|
6424
6602
|
);
|
|
6425
6603
|
} else {
|
|
6426
6604
|
const slug = opts?.githubSlug ?? "<origin-owner/name>";
|
|
6427
|
-
const
|
|
6605
|
+
const resolve2 = opts?.githubSlug ? "" : " Resolve `<origin-owner/name>` with `git remote get-url origin` in this worktree.";
|
|
6428
6606
|
lines.push(
|
|
6429
|
-
`- Prefer a draft PR first: \`gh pr create --draft -R ${slug}\` (update via \`gh pr edit -R ${slug}\`). Always pass \`-R\` \u2014 bare \`gh pr create\` may target upstream on dual-remote checkouts.${
|
|
6607
|
+
`- Prefer a draft PR first: \`gh pr create --draft -R ${slug}\` (update via \`gh pr edit -R ${slug}\`). Always pass \`-R\` \u2014 bare \`gh pr create\` may target upstream on dual-remote checkouts.${resolve2} Mark ready for review only when asked. If GitHub rejects the body as too long, retry with a short \`--body-file\` \u2014 do not paste a changelog or diff into \`--body\`.`
|
|
6430
6608
|
);
|
|
6431
6609
|
}
|
|
6432
6610
|
lines.push(
|
|
@@ -6538,9 +6716,9 @@ function formatPrGateDirective() {
|
|
|
6538
6716
|
}
|
|
6539
6717
|
function formatProcessGuideDirective(opts) {
|
|
6540
6718
|
const worktree = opts?.worktreePath?.trim();
|
|
6541
|
-
const hasGraphSkill = !!worktree && existsSync16(
|
|
6542
|
-
const hasReviewSkill = !!worktree && existsSync16(
|
|
6543
|
-
const hasInstallSkill = !!worktree && existsSync16(
|
|
6719
|
+
const hasGraphSkill = !!worktree && existsSync16(join16(worktree, ".claude/skills/graph-engineering/SKILL.md"));
|
|
6720
|
+
const hasReviewSkill = !!worktree && existsSync16(join16(worktree, ".claude/skills/review/SKILL.md"));
|
|
6721
|
+
const hasInstallSkill = !!worktree && existsSync16(join16(worktree, ".claude/skills/worktree-install/SKILL.md"));
|
|
6544
6722
|
return [
|
|
6545
6723
|
"Process guides (recurring work only):",
|
|
6546
6724
|
hasGraphSkill ? "- Migrations, ports, batch fixes, and other fan-out: follow `.claude/skills/graph-engineering/SKILL.md` (`/graph-engineering`). Judge first; state on disk; grow the rulebook; do not patch around it." : null,
|
|
@@ -6903,8 +7081,8 @@ async function syncThreadBranchFromGit(threadId) {
|
|
|
6903
7081
|
|
|
6904
7082
|
// src/store/schedules.ts
|
|
6905
7083
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
6906
|
-
import { existsSync as existsSync17, readFileSync as
|
|
6907
|
-
import { join as
|
|
7084
|
+
import { existsSync as existsSync17, readFileSync as readFileSync12 } from "fs";
|
|
7085
|
+
import { join as join17 } from "path";
|
|
6908
7086
|
import { Cron } from "croner";
|
|
6909
7087
|
var DURATION_RE = /^(\d+)(s|m|h|d)$/i;
|
|
6910
7088
|
var UNIT_MS = {
|
|
@@ -6914,7 +7092,7 @@ var UNIT_MS = {
|
|
|
6914
7092
|
d: 864e5
|
|
6915
7093
|
};
|
|
6916
7094
|
function schedulesPath() {
|
|
6917
|
-
return
|
|
7095
|
+
return join17(appDataDir(), "schedules.json");
|
|
6918
7096
|
}
|
|
6919
7097
|
function parseDurationMs(every) {
|
|
6920
7098
|
const m = every.trim().match(DURATION_RE);
|
|
@@ -7022,7 +7200,7 @@ function readAll() {
|
|
|
7022
7200
|
const path = schedulesPath();
|
|
7023
7201
|
if (!existsSync17(path)) return [];
|
|
7024
7202
|
try {
|
|
7025
|
-
const parsed = JSON.parse(
|
|
7203
|
+
const parsed = JSON.parse(readFileSync12(path, "utf8"));
|
|
7026
7204
|
if (!Array.isArray(parsed)) return [];
|
|
7027
7205
|
return parsed.map((row) => {
|
|
7028
7206
|
try {
|
|
@@ -7148,7 +7326,7 @@ function formatScheduledPrompt(name, prompt) {
|
|
|
7148
7326
|
${prompt}`;
|
|
7149
7327
|
}
|
|
7150
7328
|
async function defaultDeps() {
|
|
7151
|
-
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-
|
|
7329
|
+
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-R75VDUR2.js");
|
|
7152
7330
|
const orch = getOrchestrator2();
|
|
7153
7331
|
return {
|
|
7154
7332
|
findThread: (id) => findThreadByRef(id),
|
|
@@ -7213,14 +7391,14 @@ function armOne(schedule) {
|
|
|
7213
7391
|
if (!schedule.enabled) return;
|
|
7214
7392
|
const at = new Date(schedule.nextRunAt);
|
|
7215
7393
|
if (Number.isNaN(at.getTime())) return;
|
|
7216
|
-
const
|
|
7217
|
-
if (
|
|
7394
|
+
const delay2 = Math.max(0, at.getTime() - Date.now());
|
|
7395
|
+
if (delay2 <= 250) {
|
|
7218
7396
|
void fireSchedule(schedule.id).then(() => {
|
|
7219
7397
|
if (isThisProcessDesktopHost()) armSchedules();
|
|
7220
7398
|
});
|
|
7221
7399
|
return;
|
|
7222
7400
|
}
|
|
7223
|
-
const capped = Math.min(
|
|
7401
|
+
const capped = Math.min(delay2, SET_TIMEOUT_MAX);
|
|
7224
7402
|
const timer = setTimeout(() => {
|
|
7225
7403
|
timers.delete(schedule.id);
|
|
7226
7404
|
const live = getSchedule(schedule.id);
|
|
@@ -7604,8 +7782,8 @@ var Orchestrator = class {
|
|
|
7604
7782
|
scheduleQuotaResume(threadId, resumeAt) {
|
|
7605
7783
|
this.clearQuotaResumeTimer(threadId);
|
|
7606
7784
|
updateThread(threadId, { quotaResumeAt: resumeAt.toISOString() });
|
|
7607
|
-
const
|
|
7608
|
-
const capped = Math.min(
|
|
7785
|
+
const delay2 = Math.max(5e3, resumeAt.getTime() - Date.now());
|
|
7786
|
+
const capped = Math.min(delay2, 2147483647);
|
|
7609
7787
|
const timer = setTimeout(() => {
|
|
7610
7788
|
this.quotaResumeTimers.delete(threadId);
|
|
7611
7789
|
void this.resumeAfterQuotaWait(threadId);
|
|
@@ -8660,6 +8838,7 @@ var Orchestrator = class {
|
|
|
8660
8838
|
return { port: shared.devPort, scriptName: resolvedName, ports: [shared.devPort] };
|
|
8661
8839
|
}
|
|
8662
8840
|
}
|
|
8841
|
+
const preferredPorts = active?.ports?.length ? active.ports : active?.port != null ? [active.port] : void 0;
|
|
8663
8842
|
if (active && !existing) {
|
|
8664
8843
|
killListenersOnPorts(collectActiveRunPorts([active]));
|
|
8665
8844
|
this.syncWorktreeRuns(
|
|
@@ -8698,7 +8877,7 @@ var Orchestrator = class {
|
|
|
8698
8877
|
thread.repoPath,
|
|
8699
8878
|
thread.worktreePath,
|
|
8700
8879
|
runOutput.push,
|
|
8701
|
-
{ scriptName: resolvedName }
|
|
8880
|
+
{ scriptName: resolvedName, preferredPorts }
|
|
8702
8881
|
);
|
|
8703
8882
|
} catch (err) {
|
|
8704
8883
|
runOutput.flush();
|
|
@@ -9147,7 +9326,7 @@ var Orchestrator = class {
|
|
|
9147
9326
|
return thread;
|
|
9148
9327
|
}
|
|
9149
9328
|
const start = Date.now();
|
|
9150
|
-
return new Promise((
|
|
9329
|
+
return new Promise((resolve2, reject) => {
|
|
9151
9330
|
let settled = false;
|
|
9152
9331
|
let timer;
|
|
9153
9332
|
const finish = (fn) => {
|
|
@@ -9165,7 +9344,7 @@ var Orchestrator = class {
|
|
|
9165
9344
|
finish(() => reject(new Error(`Thread not found: ${thread.id}`)));
|
|
9166
9345
|
return;
|
|
9167
9346
|
}
|
|
9168
|
-
finish(() =>
|
|
9347
|
+
finish(() => resolve2(latest));
|
|
9169
9348
|
return;
|
|
9170
9349
|
}
|
|
9171
9350
|
if (event.type === "status_changed") {
|
|
@@ -9175,7 +9354,7 @@ var Orchestrator = class {
|
|
|
9175
9354
|
return;
|
|
9176
9355
|
}
|
|
9177
9356
|
if (!["running", "queued"].includes(latest.status)) {
|
|
9178
|
-
finish(() =>
|
|
9357
|
+
finish(() => resolve2(latest));
|
|
9179
9358
|
}
|
|
9180
9359
|
}
|
|
9181
9360
|
});
|
|
@@ -9187,12 +9366,12 @@ var Orchestrator = class {
|
|
|
9187
9366
|
}
|
|
9188
9367
|
const current = this.healStaleReportedActivity(raw);
|
|
9189
9368
|
if (!this.threadLooksLive(current)) {
|
|
9190
|
-
finish(() =>
|
|
9369
|
+
finish(() => resolve2(current));
|
|
9191
9370
|
return;
|
|
9192
9371
|
}
|
|
9193
9372
|
if (Date.now() - start > timeoutMs) {
|
|
9194
9373
|
if (opts?.resolveIfStillRunning) {
|
|
9195
|
-
finish(() =>
|
|
9374
|
+
finish(() => resolve2(current));
|
|
9196
9375
|
} else {
|
|
9197
9376
|
finish(() => reject(new Error("wait_for_turn timed out")));
|
|
9198
9377
|
}
|
|
@@ -10045,13 +10224,21 @@ export {
|
|
|
10045
10224
|
buildReviewRequestAttachment,
|
|
10046
10225
|
readExistingReviewRequestFile,
|
|
10047
10226
|
requestReview,
|
|
10227
|
+
RUN_PORTS_REL,
|
|
10228
|
+
worktreeRunPortsPath,
|
|
10229
|
+
loadWorktreeRunPorts,
|
|
10230
|
+
saveWorktreeRunPorts,
|
|
10231
|
+
PORT_RANGE_SIZE,
|
|
10048
10232
|
readWorktreeInclude,
|
|
10049
10233
|
resolveFilesToCopy,
|
|
10050
10234
|
copyConfiguredFiles,
|
|
10051
10235
|
captureLoginEnv,
|
|
10052
10236
|
buildWorkspaceScriptEnv,
|
|
10053
10237
|
killableListenerPids,
|
|
10238
|
+
listenerCwd,
|
|
10239
|
+
isOtherWorktreeListenerCwd,
|
|
10054
10240
|
killListenersOnPorts,
|
|
10241
|
+
killStaleWorktreeListeners,
|
|
10055
10242
|
runSetupScript,
|
|
10056
10243
|
runConventionSetup,
|
|
10057
10244
|
runWorkspaceSetup,
|
|
@@ -10062,6 +10249,8 @@ export {
|
|
|
10062
10249
|
getRunMode,
|
|
10063
10250
|
reservePort,
|
|
10064
10251
|
allocatePort,
|
|
10252
|
+
tryReservePort,
|
|
10253
|
+
tryReservePorts,
|
|
10065
10254
|
reservePortRange,
|
|
10066
10255
|
allocatePortRange,
|
|
10067
10256
|
startDevServer,
|