@sideboard-ai/core 0.1.51 → 0.1.53
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/agents/cursor-runner.cjs +43 -4
- package/dist/agents/cursor-runner.js +45 -6
- package/dist/{agents-HIEJL3UV.js → agents-KP7UJEHJ.js} +1 -1
- package/dist/{agents-5ROTZNCX.js → agents-KYACODJ3.js} +2 -2
- package/dist/{chunk-5ZPSH7VI.js → chunk-A6HVEMIB.js} +16 -7
- package/dist/chunk-B3SJXYIJ.js +24 -0
- package/dist/{chunk-7EUWSBWR.js → chunk-BZST4HMJ.js} +21 -6
- package/dist/{chunk-ZH5QZ4CR.js → chunk-DZFH2KLT.js} +280 -7
- package/dist/chunk-FKOIHGKV.js +21 -0
- package/dist/{chunk-E4VVEKAM.js → chunk-GNML24AW.js} +2 -2
- package/dist/{chunk-EOYDCKQC.js → chunk-HLEX5AQ6.js} +4 -0
- package/dist/{chunk-K3WMKGFY.js → chunk-LRLKJM3O.js} +2 -1
- package/dist/chunk-N5PM7HGQ.js +103 -0
- package/dist/chunk-QTUESPAW.js +101 -0
- package/dist/{chunk-O6W3P7V3.js → chunk-TSRXOSVD.js} +4 -0
- package/dist/{chunk-F4Q3IM6V.js → chunk-UEAHMGHW.js} +2 -1
- package/dist/{chunk-J5JTEJ5O.js → chunk-VG22SETP.js} +6 -0
- package/dist/{chunk-XRSAGVRW.js → chunk-XOU6HNQJ.js} +245 -7
- package/dist/{chunk-O5DOO7DP.js → chunk-XX5BB7NV.js} +3 -3
- package/dist/{chunk-QN7XNQAT.js → chunk-YDXQ72MD.js} +2 -2
- package/dist/{chunk-YFJ4FG2P.js → chunk-YOWIYAVA.js} +3 -3
- package/dist/{coordinator-prompt-7HHJRO7B.js → coordinator-prompt-6FXVTSFN.js} +4 -3
- package/dist/{coordinator-prompt-WD7FAMA2.js → coordinator-prompt-S6JZD5EF.js} +4 -3
- package/dist/{global-workspace-OJEPGDXA.js → global-workspace-EV4G2WMQ.js} +5 -4
- package/dist/{global-workspace-ECYN2MKL.js → global-workspace-MSX2K27Y.js} +5 -4
- package/dist/index.cjs +1383 -149
- package/dist/index.d.cts +389 -15
- package/dist/index.d.ts +389 -15
- package/dist/index.js +883 -91
- package/dist/mcp/run-stdio.cjs +1154 -141
- package/dist/mcp/run-stdio.js +709 -79
- package/dist/plan-file-6O7G4VPQ.js +23 -0
- package/dist/plan-file-PHVKUAEE.js +25 -0
- package/dist/{thread-store-XICUWFNM.js → thread-store-GHOADGL2.js} +1 -1
- package/dist/{thread-store-OV2X6PYO.js → thread-store-UJIGMI5J.js} +1 -1
- package/dist/{workspaces-MUU7RGVV.js → workspaces-3RQQZQRO.js} +6 -5
- package/dist/{workspaces-ZWOOFZUV.js → workspaces-AYTBR6KQ.js} +6 -5
- package/dist/{worktree-DVNDMWZ7.js → worktree-5KEQWSAF.js} +5 -2
- package/dist/{worktree-GDV56MX4.js → worktree-RWGL7FUV.js} +5 -2
- package/package.json +1 -1
package/dist/mcp/run-stdio.cjs
CHANGED
|
@@ -92,6 +92,11 @@ function summarizeTurnStderr(tail, maxChars = 500) {
|
|
|
92
92
|
if (joined.length <= maxChars) return joined;
|
|
93
93
|
return joined.slice(joined.length - maxChars);
|
|
94
94
|
}
|
|
95
|
+
function looksLikeInvalidAgentSession(text) {
|
|
96
|
+
const lower = text.trim().toLowerCase();
|
|
97
|
+
if (!lower) return false;
|
|
98
|
+
return /session not found/.test(lower) || /no conversation found/.test(lower) || /conversation .+ not found/.test(lower) || /thread .+ not found/.test(lower) || /unknown session/.test(lower) || /invalid session/.test(lower) || /session .+ (missing|expired|deleted|gone)/.test(lower) || /cannot resume/.test(lower) || /failed to (load|resume|open) session/.test(lower);
|
|
99
|
+
}
|
|
95
100
|
function looksLikeAgentFailureMessage(text) {
|
|
96
101
|
const lower = text.trim().toLowerCase();
|
|
97
102
|
if (!lower) return false;
|
|
@@ -481,6 +486,8 @@ function normalizeThread(raw) {
|
|
|
481
486
|
agentPid: raw.agentPid ?? null,
|
|
482
487
|
attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
|
|
483
488
|
prTitle: raw.prTitle ?? null,
|
|
489
|
+
stackId: raw.stackId ?? null,
|
|
490
|
+
stackLayer: raw.stackLayer ?? null,
|
|
484
491
|
userSetTitle: Boolean(raw.userSetTitle),
|
|
485
492
|
activeRuns: Array.isArray(raw.activeRuns) ? raw.activeRuns : [],
|
|
486
493
|
quotaResumeAt: raw.quotaResumeAt ?? null,
|
|
@@ -505,6 +512,8 @@ function createEmptyThread(partial) {
|
|
|
505
512
|
activeRuns: partial.activeRuns ?? [],
|
|
506
513
|
prUrl: partial.prUrl ?? null,
|
|
507
514
|
prTitle: partial.prTitle ?? null,
|
|
515
|
+
stackId: partial.stackId ?? null,
|
|
516
|
+
stackLayer: partial.stackLayer ?? null,
|
|
508
517
|
userSetTitle: partial.userSetTitle ?? false,
|
|
509
518
|
messages: partial.messages ?? [],
|
|
510
519
|
attachments: partial.attachments ?? [],
|
|
@@ -1688,6 +1697,206 @@ var init_pr_gates = __esm({
|
|
|
1688
1697
|
}
|
|
1689
1698
|
});
|
|
1690
1699
|
|
|
1700
|
+
// src/git/stack.ts
|
|
1701
|
+
async function detectGhStack(cwd) {
|
|
1702
|
+
const now = Date.now();
|
|
1703
|
+
if (cachedStatus && now - cachedStatus.at < STATUS_TTL_MS) {
|
|
1704
|
+
return cachedStatus.status;
|
|
1705
|
+
}
|
|
1706
|
+
const probe = await gh(["stack", "view", "--help"], cwd, { reject: false });
|
|
1707
|
+
if (probe.exitCode === 0) {
|
|
1708
|
+
const status2 = { available: true };
|
|
1709
|
+
cachedStatus = { at: now, status: status2 };
|
|
1710
|
+
return status2;
|
|
1711
|
+
}
|
|
1712
|
+
const err = `${probe.stderr}
|
|
1713
|
+
${probe.stdout}`;
|
|
1714
|
+
const reason = /official extension|extension install|github\/gh-stack/i.test(err) ? "Install with: gh extension install github/gh-stack" : err.trim() || "gh stack is not available";
|
|
1715
|
+
const status = { available: false, reason };
|
|
1716
|
+
cachedStatus = { at: now, status };
|
|
1717
|
+
return status;
|
|
1718
|
+
}
|
|
1719
|
+
function str(v) {
|
|
1720
|
+
return typeof v === "string" ? v : v == null ? "" : String(v);
|
|
1721
|
+
}
|
|
1722
|
+
function num(v) {
|
|
1723
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
1724
|
+
if (typeof v === "string" && v.trim() && Number.isFinite(Number(v))) {
|
|
1725
|
+
return Number(v);
|
|
1726
|
+
}
|
|
1727
|
+
return null;
|
|
1728
|
+
}
|
|
1729
|
+
function parseGhStackViewJson(raw) {
|
|
1730
|
+
let data;
|
|
1731
|
+
try {
|
|
1732
|
+
data = JSON.parse(raw);
|
|
1733
|
+
} catch {
|
|
1734
|
+
return null;
|
|
1735
|
+
}
|
|
1736
|
+
if (!Array.isArray(data.branches) || data.branches.length === 0) return null;
|
|
1737
|
+
const trunk = str(data.trunk) || "main";
|
|
1738
|
+
const currentBranch2 = str(data.currentBranch);
|
|
1739
|
+
const stackNumber = num(data.stackNumber) ?? num(data.number);
|
|
1740
|
+
const layers = [];
|
|
1741
|
+
for (let i = 0; i < data.branches.length; i++) {
|
|
1742
|
+
const b = data.branches[i];
|
|
1743
|
+
if (!b || typeof b !== "object") continue;
|
|
1744
|
+
const name = str(b.name);
|
|
1745
|
+
if (!name) continue;
|
|
1746
|
+
const pr = b.pr && typeof b.pr === "object" ? b.pr : null;
|
|
1747
|
+
layers.push({
|
|
1748
|
+
position: i + 1,
|
|
1749
|
+
branchName: name,
|
|
1750
|
+
headSha: str(b.head) || void 0,
|
|
1751
|
+
baseSha: str(b.base) || void 0,
|
|
1752
|
+
isCurrent: Boolean(b.isCurrent) || name === currentBranch2,
|
|
1753
|
+
isMerged: Boolean(b.isMerged),
|
|
1754
|
+
isQueued: Boolean(b.isQueued),
|
|
1755
|
+
needsRebase: Boolean(b.needsRebase),
|
|
1756
|
+
prNumber: pr ? num(pr.number) : null,
|
|
1757
|
+
prUrl: pr && str(pr.url) ? str(pr.url) : null,
|
|
1758
|
+
prState: pr && str(pr.state) ? str(pr.state).toUpperCase() : null,
|
|
1759
|
+
title: pr && str(pr.title) ? str(pr.title) : void 0
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
if (!layers.length) return null;
|
|
1763
|
+
let currentIndex = layers.findIndex((l) => l.isCurrent);
|
|
1764
|
+
if (currentIndex < 0 && currentBranch2) {
|
|
1765
|
+
currentIndex = layers.findIndex((l) => l.branchName === currentBranch2);
|
|
1766
|
+
}
|
|
1767
|
+
const { readyToMerge, blockedReason } = stackMergeReadiness(layers, currentIndex);
|
|
1768
|
+
return {
|
|
1769
|
+
stackNumber,
|
|
1770
|
+
trunk,
|
|
1771
|
+
currentBranch: currentBranch2 || layers[currentIndex]?.branchName || layers[0].branchName,
|
|
1772
|
+
layers,
|
|
1773
|
+
currentIndex,
|
|
1774
|
+
readyToMerge,
|
|
1775
|
+
blockedReason
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
function stackMergeReadiness(layers, throughIndex) {
|
|
1779
|
+
if (throughIndex < 0 || throughIndex >= layers.length) {
|
|
1780
|
+
return { readyToMerge: false, blockedReason: "Not on a stack layer" };
|
|
1781
|
+
}
|
|
1782
|
+
for (let i = 0; i <= throughIndex; i++) {
|
|
1783
|
+
const layer = layers[i];
|
|
1784
|
+
if (layer.isMerged) continue;
|
|
1785
|
+
if (!layer.prNumber) {
|
|
1786
|
+
return {
|
|
1787
|
+
readyToMerge: false,
|
|
1788
|
+
blockedReason: `Layer ${layer.branchName} has no pull request yet`
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
if (layer.needsRebase) {
|
|
1792
|
+
return {
|
|
1793
|
+
readyToMerge: false,
|
|
1794
|
+
blockedReason: `PR #${layer.prNumber} needs rebase`
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
const state = (layer.prState ?? "").toUpperCase();
|
|
1798
|
+
if (state && state !== "OPEN" && state !== "QUEUED") {
|
|
1799
|
+
return {
|
|
1800
|
+
readyToMerge: false,
|
|
1801
|
+
blockedReason: `PR #${layer.prNumber} is ${state}`
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
return { readyToMerge: true, blockedReason: null };
|
|
1806
|
+
}
|
|
1807
|
+
async function getPrStack(cwd) {
|
|
1808
|
+
const status = await detectGhStack(cwd);
|
|
1809
|
+
if (!status.available) return null;
|
|
1810
|
+
const result = await gh(["stack", "view", "--json"], cwd, { reject: false });
|
|
1811
|
+
if (result.exitCode === 2) return null;
|
|
1812
|
+
if (result.exitCode !== 0) {
|
|
1813
|
+
if (/not in a stack|no stack/i.test(`${result.stderr}
|
|
1814
|
+
${result.stdout}`)) {
|
|
1815
|
+
return null;
|
|
1816
|
+
}
|
|
1817
|
+
if (result.exitCode === 9) return null;
|
|
1818
|
+
return null;
|
|
1819
|
+
}
|
|
1820
|
+
const json = result.stdout.trim();
|
|
1821
|
+
if (!json) return null;
|
|
1822
|
+
return parseGhStackViewJson(json);
|
|
1823
|
+
}
|
|
1824
|
+
async function mergePrStack(cwd, opts) {
|
|
1825
|
+
const status = await detectGhStack(cwd);
|
|
1826
|
+
if (!status.available) {
|
|
1827
|
+
throw new Error(status.reason);
|
|
1828
|
+
}
|
|
1829
|
+
const method = opts.method ?? "squash";
|
|
1830
|
+
const methodFlag = method === "rebase" ? "--rebase" : method === "merge" ? "--merge" : "--squash";
|
|
1831
|
+
const args = [
|
|
1832
|
+
"stack",
|
|
1833
|
+
"merge",
|
|
1834
|
+
String(opts.through),
|
|
1835
|
+
"--yes",
|
|
1836
|
+
methodFlag
|
|
1837
|
+
];
|
|
1838
|
+
const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
|
|
1839
|
+
if (exitCode !== 0) {
|
|
1840
|
+
throw new Error(stderr.trim() || stdout.trim() || "gh stack merge failed");
|
|
1841
|
+
}
|
|
1842
|
+
return { stdout };
|
|
1843
|
+
}
|
|
1844
|
+
async function initPrStack(cwd, branches, opts) {
|
|
1845
|
+
if (!branches.length) throw new Error("initPrStack requires at least one branch name");
|
|
1846
|
+
const status = await detectGhStack(cwd);
|
|
1847
|
+
if (!status.available) throw new Error(status.reason);
|
|
1848
|
+
const args = ["stack", "init"];
|
|
1849
|
+
if (opts?.base) args.push("--base", opts.base);
|
|
1850
|
+
args.push(...branches);
|
|
1851
|
+
const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
|
|
1852
|
+
if (exitCode !== 0) {
|
|
1853
|
+
throw new Error(stderr.trim() || stdout.trim() || "gh stack init failed");
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
async function addPrStackLayer(cwd, branchName) {
|
|
1857
|
+
if (!branchName.trim()) throw new Error("branch name required");
|
|
1858
|
+
const status = await detectGhStack(cwd);
|
|
1859
|
+
if (!status.available) throw new Error(status.reason);
|
|
1860
|
+
const { exitCode, stderr, stdout } = await gh(
|
|
1861
|
+
["stack", "add", branchName.trim()],
|
|
1862
|
+
cwd,
|
|
1863
|
+
{ reject: false }
|
|
1864
|
+
);
|
|
1865
|
+
if (exitCode !== 0) {
|
|
1866
|
+
throw new Error(stderr.trim() || stdout.trim() || "gh stack add failed");
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
var cachedStatus, STATUS_TTL_MS;
|
|
1870
|
+
var init_stack = __esm({
|
|
1871
|
+
"src/git/stack.ts"() {
|
|
1872
|
+
"use strict";
|
|
1873
|
+
init_run();
|
|
1874
|
+
cachedStatus = null;
|
|
1875
|
+
STATUS_TTL_MS = 6e4;
|
|
1876
|
+
}
|
|
1877
|
+
});
|
|
1878
|
+
|
|
1879
|
+
// src/paths/workspace-scratch.ts
|
|
1880
|
+
function attachmentsGitignoreBody() {
|
|
1881
|
+
return ATTACHMENTS_GITIGNORE;
|
|
1882
|
+
}
|
|
1883
|
+
function isWorkspaceScratchPath(relativePath) {
|
|
1884
|
+
const p = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
|
|
1885
|
+
return p === ATTACHMENTS_DIR || p.startsWith(`${ATTACHMENTS_DIR}/`) || p === LEGACY_ATTACHMENTS_DIR || p.startsWith(`${LEGACY_ATTACHMENTS_DIR}/`) || p === ".context" || p.startsWith(".context/");
|
|
1886
|
+
}
|
|
1887
|
+
var ATTACHMENTS_DIR, LEGACY_ATTACHMENTS_DIR, ATTACHMENTS_GITIGNORE;
|
|
1888
|
+
var init_workspace_scratch = __esm({
|
|
1889
|
+
"src/paths/workspace-scratch.ts"() {
|
|
1890
|
+
"use strict";
|
|
1891
|
+
ATTACHMENTS_DIR = ".context/attachments";
|
|
1892
|
+
LEGACY_ATTACHMENTS_DIR = ".sideboard/attachments";
|
|
1893
|
+
ATTACHMENTS_GITIGNORE = `# Sideboard / workspace attachments (local only)
|
|
1894
|
+
*
|
|
1895
|
+
!.gitignore
|
|
1896
|
+
`;
|
|
1897
|
+
}
|
|
1898
|
+
});
|
|
1899
|
+
|
|
1691
1900
|
// src/git/worktree.ts
|
|
1692
1901
|
var worktree_exports = {};
|
|
1693
1902
|
__export(worktree_exports, {
|
|
@@ -1697,6 +1906,7 @@ __export(worktree_exports, {
|
|
|
1697
1906
|
branchDisplayLabel: () => branchDisplayLabel,
|
|
1698
1907
|
collectTakenTeamSlugs: () => collectTakenTeamSlugs,
|
|
1699
1908
|
commitAll: () => commitAll,
|
|
1909
|
+
createExistingBranchWorktree: () => createExistingBranchWorktree,
|
|
1700
1910
|
createOrUpdatePr: () => createOrUpdatePr,
|
|
1701
1911
|
createThreadWorktree: () => createThreadWorktree,
|
|
1702
1912
|
currentBranch: () => currentBranch,
|
|
@@ -2410,6 +2620,52 @@ ${add.stdout}`;
|
|
|
2410
2620
|
await ensureGhPreferOrigin(worktreePath);
|
|
2411
2621
|
return { branchName, worktreePath };
|
|
2412
2622
|
}
|
|
2623
|
+
async function createExistingBranchWorktree(opts) {
|
|
2624
|
+
const branchName = opts.branchName.trim();
|
|
2625
|
+
if (!branchName) throw new Error("branch name required");
|
|
2626
|
+
const worktreePath = (0, import_node_path5.join)(worktreesRoot(opts.repoPath), opts.slug);
|
|
2627
|
+
if ((0, import_node_fs4.existsSync)(worktreePath)) {
|
|
2628
|
+
throw new Error(`Worktree already exists at ${worktreePath}`);
|
|
2629
|
+
}
|
|
2630
|
+
await ensureGhPreferOrigin(opts.repoPath);
|
|
2631
|
+
await git(["fetch", "origin", "--prune"], opts.repoPath, { reject: false });
|
|
2632
|
+
if (!branchName.startsWith("origin/") && !branchName.startsWith("refs/")) {
|
|
2633
|
+
await git(["fetch", "origin", branchName], opts.repoPath, { reject: false });
|
|
2634
|
+
}
|
|
2635
|
+
const existing = await listWorktrees(opts.repoPath);
|
|
2636
|
+
const already = existing.find((w) => w.branch === branchName);
|
|
2637
|
+
if (already?.path) {
|
|
2638
|
+
throw new Error(
|
|
2639
|
+
`Branch ${branchName} is already checked out at ${already.path}`
|
|
2640
|
+
);
|
|
2641
|
+
}
|
|
2642
|
+
const startPoint = await resolveWorktreeStartPoint(opts.repoPath, branchName);
|
|
2643
|
+
const add = await git(
|
|
2644
|
+
["worktree", "add", worktreePath, startPoint],
|
|
2645
|
+
opts.repoPath,
|
|
2646
|
+
{ reject: false }
|
|
2647
|
+
);
|
|
2648
|
+
if (add.exitCode !== 0) {
|
|
2649
|
+
const retry = await git(
|
|
2650
|
+
["worktree", "add", worktreePath, branchName],
|
|
2651
|
+
opts.repoPath,
|
|
2652
|
+
{ reject: false }
|
|
2653
|
+
);
|
|
2654
|
+
if (retry.exitCode !== 0) {
|
|
2655
|
+
throw new Error(
|
|
2656
|
+
`Failed to create worktree for ${branchName}: ${retry.stderr.trim() || add.stderr.trim() || retry.stdout.trim() || add.stdout.trim() || `exit ${add.exitCode}`}`
|
|
2657
|
+
);
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
const head = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath, {
|
|
2661
|
+
reject: false
|
|
2662
|
+
});
|
|
2663
|
+
if (head.stdout.trim() === "HEAD" || head.stdout.trim() !== branchName) {
|
|
2664
|
+
await git(["checkout", "-B", branchName], worktreePath, { reject: false });
|
|
2665
|
+
}
|
|
2666
|
+
await ensureGhPreferOrigin(worktreePath);
|
|
2667
|
+
return { branchName, worktreePath };
|
|
2668
|
+
}
|
|
2413
2669
|
async function removeWorktree(repoPath, worktreePath, opts) {
|
|
2414
2670
|
await git(["worktree", "remove", "--force", worktreePath], repoPath, {
|
|
2415
2671
|
reject: false
|
|
@@ -2449,8 +2705,7 @@ async function isDirty(worktreePath) {
|
|
|
2449
2705
|
return false;
|
|
2450
2706
|
}
|
|
2451
2707
|
function isSideboardScratchPath(relativePath) {
|
|
2452
|
-
|
|
2453
|
-
return p === ".sideboard/attachments" || p.startsWith(".sideboard/attachments/");
|
|
2708
|
+
return isWorkspaceScratchPath(relativePath);
|
|
2454
2709
|
}
|
|
2455
2710
|
function porcelainStatusPath(line) {
|
|
2456
2711
|
const rest = line.length >= 3 ? line.slice(3) : "";
|
|
@@ -2478,7 +2733,7 @@ async function pushBranch(worktreePath, branchName) {
|
|
|
2478
2733
|
}
|
|
2479
2734
|
async function mergePr(cwd, selector, opts) {
|
|
2480
2735
|
const slug = await resolveGithubRepoSlug(cwd);
|
|
2481
|
-
const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft"];
|
|
2736
|
+
const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft,number"];
|
|
2482
2737
|
if (slug) viewArgs.push("--repo", slug);
|
|
2483
2738
|
const before = await gh(viewArgs, cwd, { reject: false });
|
|
2484
2739
|
if (before.exitCode !== 0 || !before.stdout.trim()) {
|
|
@@ -2486,16 +2741,29 @@ async function mergePr(cwd, selector, opts) {
|
|
|
2486
2741
|
}
|
|
2487
2742
|
let url = "";
|
|
2488
2743
|
let isDraft = false;
|
|
2744
|
+
let prNumber = null;
|
|
2489
2745
|
try {
|
|
2490
2746
|
const parsed = JSON.parse(before.stdout);
|
|
2491
2747
|
url = String(parsed.url ?? "");
|
|
2492
2748
|
isDraft = Boolean(parsed.isDraft);
|
|
2749
|
+
prNumber = typeof parsed.number === "number" && Number.isFinite(parsed.number) ? parsed.number : null;
|
|
2493
2750
|
if (String(parsed.state ?? "").toUpperCase() === "MERGED") {
|
|
2494
2751
|
return { url, state: "MERGED" };
|
|
2495
2752
|
}
|
|
2496
2753
|
} catch {
|
|
2497
2754
|
throw new Error("Could not parse pull request details");
|
|
2498
2755
|
}
|
|
2756
|
+
const stack = await getPrStack(cwd);
|
|
2757
|
+
const stackLayer = stack && prNumber != null ? stack.layers.find((l) => l.prNumber === prNumber) : null;
|
|
2758
|
+
if (stack && stackLayer && prNumber != null) {
|
|
2759
|
+
const throughIndex = stack.layers.findIndex((l) => l.prNumber === prNumber);
|
|
2760
|
+
const gate = stackMergeReadiness(stack.layers, throughIndex);
|
|
2761
|
+
if (!gate.readyToMerge) {
|
|
2762
|
+
throw new Error(gate.blockedReason || "Stack is not ready to merge");
|
|
2763
|
+
}
|
|
2764
|
+
await mergePrStack(cwd, { through: prNumber, method: opts?.method ?? "squash" });
|
|
2765
|
+
return { url, state: "MERGED" };
|
|
2766
|
+
}
|
|
2499
2767
|
if (isDraft) {
|
|
2500
2768
|
const readyArgs = ["pr", "ready", selector];
|
|
2501
2769
|
if (slug) readyArgs.push("--repo", slug);
|
|
@@ -2517,10 +2785,10 @@ async function mergePr(cwd, selector, opts) {
|
|
|
2517
2785
|
const after = await gh(viewArgs, cwd, { reject: false });
|
|
2518
2786
|
if (after.exitCode === 0 && after.stdout.trim()) {
|
|
2519
2787
|
try {
|
|
2520
|
-
const parsed = JSON.parse(after.stdout);
|
|
2788
|
+
const parsed = after.stdout ? JSON.parse(after.stdout) : null;
|
|
2521
2789
|
return {
|
|
2522
|
-
url: String(parsed
|
|
2523
|
-
state: String(parsed
|
|
2790
|
+
url: String(parsed?.url ?? url),
|
|
2791
|
+
state: String(parsed?.state ?? "MERGED")
|
|
2524
2792
|
};
|
|
2525
2793
|
} catch {
|
|
2526
2794
|
}
|
|
@@ -2668,7 +2936,9 @@ var init_worktree = __esm({
|
|
|
2668
2936
|
init_gh_errors();
|
|
2669
2937
|
init_run();
|
|
2670
2938
|
init_pr_gates();
|
|
2939
|
+
init_stack();
|
|
2671
2940
|
init_teams();
|
|
2941
|
+
init_workspace_scratch();
|
|
2672
2942
|
init_worktree_labels();
|
|
2673
2943
|
}
|
|
2674
2944
|
});
|
|
@@ -3346,6 +3616,7 @@ var init_coordinator_prompt = __esm({
|
|
|
3346
3616
|
"Discover:",
|
|
3347
3617
|
"- list_workspaces \u2014 registered repos (path + github slug when known)",
|
|
3348
3618
|
"- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces (issues: Linear API or GitHub Issues)",
|
|
3619
|
+
"- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
|
|
3349
3620
|
"- list_models \u2014 only when you need a specific model (rare); otherwise leave model unset = Auto",
|
|
3350
3621
|
"- list_threads / get_thread \u2014 fleet status (what is going on)",
|
|
3351
3622
|
"Workspaces:",
|
|
@@ -4331,7 +4602,9 @@ var init_injected_mcp = __esm({
|
|
|
4331
4602
|
SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS = [
|
|
4332
4603
|
"mcp__sideboard__present_artifact",
|
|
4333
4604
|
"mcp__sideboard__present_schema",
|
|
4334
|
-
"mcp__sideboard__present_files"
|
|
4605
|
+
"mcp__sideboard__present_files",
|
|
4606
|
+
"mcp__sideboard__ask_user",
|
|
4607
|
+
"mcp__sideboard__present_plan"
|
|
4335
4608
|
];
|
|
4336
4609
|
brightsyMcpCommandCache = null;
|
|
4337
4610
|
}
|
|
@@ -4370,7 +4643,7 @@ var PLAN_MODE_INSTRUCTION;
|
|
|
4370
4643
|
var init_types = __esm({
|
|
4371
4644
|
"src/agents/types.ts"() {
|
|
4372
4645
|
"use strict";
|
|
4373
|
-
PLAN_MODE_INSTRUCTION = "Plan mode is active and must remain active until the user turns Plan mode off in the UI (or
|
|
4646
|
+
PLAN_MODE_INSTRUCTION = "Plan mode is active and must remain active until the user turns Plan mode off in the UI (or Approves / Hands off the plan). Analyze the codebase, search and read files as needed, and produce or refine a clear implementation plan. Do not modify, create, or delete any project files except via Sideboard MCP present_plan (writes .context/attachments/plan.md). When you need a clarifying decision (approach forks, auth choice, scope): (1) first write a short chat message that explains the decision and what each option means (tradeoffs, when to pick it) \u2014 do not leave the user staring at bare labels; (2) then call Sideboard MCP ask_user with the same options, including a description on every option. Sideboard shows questions in the composer and mirrors them in chat. After ask_user, wait for the user's next message with their answers before finalizing the plan. When the plan is ready for approval: (1) call present_plan with the full markdown plan (title + content) so Sideboard saves .context/attachments/plan.md and shows it in chat for Approve / Hand off / Copy; (2) Claude should also call ExitPlanMode after present_plan. Do not skip present_plan \u2014 the plan must be a markdown file, not only chat prose.";
|
|
4374
4647
|
}
|
|
4375
4648
|
});
|
|
4376
4649
|
|
|
@@ -5442,6 +5715,7 @@ var init_opencode = __esm({
|
|
|
5442
5715
|
}
|
|
5443
5716
|
},
|
|
5444
5717
|
async resolveSessionId(worktreePath, cached) {
|
|
5718
|
+
const cachedId = cached?.trim() || null;
|
|
5445
5719
|
const listed = await run(
|
|
5446
5720
|
"opencode",
|
|
5447
5721
|
["session", "list", "--format", "json"],
|
|
@@ -5453,15 +5727,21 @@ var init_opencode = __esm({
|
|
|
5453
5727
|
if (Array.isArray(sessions) && sessions.length > 0) {
|
|
5454
5728
|
const norm = (p) => p.replace(/\/+$/, "");
|
|
5455
5729
|
const wt = norm(worktreePath);
|
|
5456
|
-
const
|
|
5730
|
+
const forWorktree = sessions.filter(
|
|
5457
5731
|
(s) => s.directory && norm(s.directory) === wt || s.path && norm(s.path) === wt
|
|
5458
5732
|
);
|
|
5459
|
-
if (
|
|
5733
|
+
if (cachedId && forWorktree.some((s) => s.id === cachedId)) {
|
|
5734
|
+
return cachedId;
|
|
5735
|
+
}
|
|
5736
|
+
if (cachedId && sessions.some((s) => s.id === cachedId)) {
|
|
5737
|
+
return cachedId;
|
|
5738
|
+
}
|
|
5739
|
+
return null;
|
|
5460
5740
|
}
|
|
5461
5741
|
} catch {
|
|
5462
5742
|
}
|
|
5463
5743
|
}
|
|
5464
|
-
return
|
|
5744
|
+
return cachedId;
|
|
5465
5745
|
},
|
|
5466
5746
|
async buildAttach(thread) {
|
|
5467
5747
|
const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
|
|
@@ -6078,6 +6358,116 @@ var init_workspaces = __esm({
|
|
|
6078
6358
|
}
|
|
6079
6359
|
});
|
|
6080
6360
|
|
|
6361
|
+
// src/plan/plan-present.ts
|
|
6362
|
+
function asRecord2(v) {
|
|
6363
|
+
return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
6364
|
+
}
|
|
6365
|
+
function isPresentPlanToolName(name) {
|
|
6366
|
+
if (!name) return false;
|
|
6367
|
+
return /present_plan$/i.test(name) || /^mcp__sideboard__present_plan$/i.test(name);
|
|
6368
|
+
}
|
|
6369
|
+
function extractPresentedPlan(parts) {
|
|
6370
|
+
if (!parts?.length) return null;
|
|
6371
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
6372
|
+
const p = parts[i];
|
|
6373
|
+
if (p.type !== "tool" || !isPresentPlanToolName(p.name)) continue;
|
|
6374
|
+
const input = asRecord2(p.input) ?? {};
|
|
6375
|
+
const content = typeof input.content === "string" ? input.content : typeof input.plan === "string" ? input.plan : typeof input.markdown === "string" ? input.markdown : "";
|
|
6376
|
+
if (!content.trim()) continue;
|
|
6377
|
+
const title = typeof input.title === "string" && input.title.trim() ? input.title.trim() : "Plan";
|
|
6378
|
+
const path = typeof input.path === "string" && input.path.trim() ? input.path.trim() : PLAN_FILE_REL;
|
|
6379
|
+
return { title, content: content.trim(), path, source: "present_plan" };
|
|
6380
|
+
}
|
|
6381
|
+
return null;
|
|
6382
|
+
}
|
|
6383
|
+
function resolvePlanMarkdown(opts) {
|
|
6384
|
+
const fromTool = extractPresentedPlan(opts.parts);
|
|
6385
|
+
if (fromTool) return fromTool;
|
|
6386
|
+
const file = opts.fileContent?.trim();
|
|
6387
|
+
if (file) {
|
|
6388
|
+
return {
|
|
6389
|
+
title: "Plan",
|
|
6390
|
+
content: file,
|
|
6391
|
+
path: PLAN_FILE_REL,
|
|
6392
|
+
source: "exit_plan"
|
|
6393
|
+
};
|
|
6394
|
+
}
|
|
6395
|
+
const text = opts.text?.trim();
|
|
6396
|
+
if (text && text.length >= 80) {
|
|
6397
|
+
return {
|
|
6398
|
+
title: "Plan",
|
|
6399
|
+
content: text,
|
|
6400
|
+
path: PLAN_FILE_REL,
|
|
6401
|
+
source: "text"
|
|
6402
|
+
};
|
|
6403
|
+
}
|
|
6404
|
+
return null;
|
|
6405
|
+
}
|
|
6406
|
+
var PLAN_FILE_REL, PLAN_FILE_NAME, LEGACY_PLAN_FILE_REL;
|
|
6407
|
+
var init_plan_present = __esm({
|
|
6408
|
+
"src/plan/plan-present.ts"() {
|
|
6409
|
+
"use strict";
|
|
6410
|
+
init_workspace_scratch();
|
|
6411
|
+
PLAN_FILE_REL = `${ATTACHMENTS_DIR}/plan.md`;
|
|
6412
|
+
PLAN_FILE_NAME = "plan.md";
|
|
6413
|
+
LEGACY_PLAN_FILE_REL = ".sideboard/plan.md";
|
|
6414
|
+
}
|
|
6415
|
+
});
|
|
6416
|
+
|
|
6417
|
+
// src/plan/plan-file.ts
|
|
6418
|
+
var plan_file_exports = {};
|
|
6419
|
+
__export(plan_file_exports, {
|
|
6420
|
+
LEGACY_PLAN_FILE_REL: () => LEGACY_PLAN_FILE_REL,
|
|
6421
|
+
PLAN_FILE_NAME: () => PLAN_FILE_NAME,
|
|
6422
|
+
PLAN_FILE_REL: () => PLAN_FILE_REL,
|
|
6423
|
+
extractPresentedPlan: () => extractPresentedPlan,
|
|
6424
|
+
isPresentPlanToolName: () => isPresentPlanToolName,
|
|
6425
|
+
planFileAbs: () => planFileAbs,
|
|
6426
|
+
readPlanFile: () => readPlanFile,
|
|
6427
|
+
resolvePlanMarkdown: () => resolvePlanMarkdown,
|
|
6428
|
+
writePlanFile: () => writePlanFile
|
|
6429
|
+
});
|
|
6430
|
+
function ensureAttachmentsGitignore2(worktreePath) {
|
|
6431
|
+
const gitignoreAbs = (0, import_node_path24.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
|
|
6432
|
+
if ((0, import_node_fs26.existsSync)(gitignoreAbs)) return;
|
|
6433
|
+
(0, import_node_fs26.mkdirSync)((0, import_node_path24.dirname)(gitignoreAbs), { recursive: true });
|
|
6434
|
+
(0, import_node_fs26.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
|
|
6435
|
+
}
|
|
6436
|
+
function planFileAbs(worktreePath) {
|
|
6437
|
+
return (0, import_node_path24.join)(worktreePath, PLAN_FILE_REL);
|
|
6438
|
+
}
|
|
6439
|
+
function readTextIfPresent2(abs) {
|
|
6440
|
+
if (!(0, import_node_fs26.existsSync)(abs)) return null;
|
|
6441
|
+
try {
|
|
6442
|
+
const content = (0, import_node_fs26.readFileSync)(abs, "utf8");
|
|
6443
|
+
return content.trim() ? content : null;
|
|
6444
|
+
} catch {
|
|
6445
|
+
return null;
|
|
6446
|
+
}
|
|
6447
|
+
}
|
|
6448
|
+
function readPlanFile(worktreePath) {
|
|
6449
|
+
return readTextIfPresent2(planFileAbs(worktreePath)) ?? readTextIfPresent2((0, import_node_path24.join)(worktreePath, `${LEGACY_ATTACHMENTS_DIR}/plan.md`)) ?? readTextIfPresent2((0, import_node_path24.join)(worktreePath, LEGACY_PLAN_FILE_REL));
|
|
6450
|
+
}
|
|
6451
|
+
function writePlanFile(worktreePath, content) {
|
|
6452
|
+
ensureAttachmentsGitignore2(worktreePath);
|
|
6453
|
+
const abs = planFileAbs(worktreePath);
|
|
6454
|
+
(0, import_node_fs26.mkdirSync)((0, import_node_path24.dirname)(abs), { recursive: true });
|
|
6455
|
+
const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
|
|
6456
|
+
(0, import_node_fs26.writeFileSync)(abs, body, "utf8");
|
|
6457
|
+
return PLAN_FILE_REL;
|
|
6458
|
+
}
|
|
6459
|
+
var import_node_fs26, import_node_path24;
|
|
6460
|
+
var init_plan_file = __esm({
|
|
6461
|
+
"src/plan/plan-file.ts"() {
|
|
6462
|
+
"use strict";
|
|
6463
|
+
import_node_fs26 = require("fs");
|
|
6464
|
+
import_node_path24 = require("path");
|
|
6465
|
+
init_workspace_scratch();
|
|
6466
|
+
init_plan_present();
|
|
6467
|
+
init_plan_present();
|
|
6468
|
+
}
|
|
6469
|
+
});
|
|
6470
|
+
|
|
6081
6471
|
// src/agents/cursor-recover.ts
|
|
6082
6472
|
var cursor_recover_exports = {};
|
|
6083
6473
|
__export(cursor_recover_exports, {
|
|
@@ -6086,10 +6476,10 @@ __export(cursor_recover_exports, {
|
|
|
6086
6476
|
function recoverFinishedCursorRun(opts) {
|
|
6087
6477
|
const agentId = opts.agentId.trim();
|
|
6088
6478
|
if (!agentId) return null;
|
|
6089
|
-
const runsPath = (0,
|
|
6090
|
-
if (!(0,
|
|
6479
|
+
const runsPath = (0, import_node_path25.join)(appDataDir(), "cursor-sdk-store", "runs.ndjson");
|
|
6480
|
+
if (!(0, import_node_fs27.existsSync)(runsPath)) return null;
|
|
6091
6481
|
try {
|
|
6092
|
-
const lines = (0,
|
|
6482
|
+
const lines = (0, import_node_fs27.readFileSync)(runsPath, "utf8").split("\n");
|
|
6093
6483
|
let best = null;
|
|
6094
6484
|
for (const line of lines) {
|
|
6095
6485
|
const trimmed = line.trim();
|
|
@@ -6115,12 +6505,12 @@ function recoverFinishedCursorRun(opts) {
|
|
|
6115
6505
|
return null;
|
|
6116
6506
|
}
|
|
6117
6507
|
}
|
|
6118
|
-
var
|
|
6508
|
+
var import_node_fs27, import_node_path25;
|
|
6119
6509
|
var init_cursor_recover = __esm({
|
|
6120
6510
|
"src/agents/cursor-recover.ts"() {
|
|
6121
6511
|
"use strict";
|
|
6122
|
-
|
|
6123
|
-
|
|
6512
|
+
import_node_fs27 = require("fs");
|
|
6513
|
+
import_node_path25 = require("path");
|
|
6124
6514
|
init_paths();
|
|
6125
6515
|
}
|
|
6126
6516
|
});
|
|
@@ -6129,11 +6519,11 @@ var init_cursor_recover = __esm({
|
|
|
6129
6519
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
6130
6520
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6131
6521
|
var import_zod = require("zod");
|
|
6132
|
-
var
|
|
6522
|
+
var import_node_path26 = require("path");
|
|
6133
6523
|
|
|
6134
6524
|
// src/orchestrator/orchestrator.ts
|
|
6135
6525
|
var import_node_events = require("events");
|
|
6136
|
-
var
|
|
6526
|
+
var import_node_fs28 = require("fs");
|
|
6137
6527
|
init_error_detail();
|
|
6138
6528
|
|
|
6139
6529
|
// src/agents/spawn.ts
|
|
@@ -6153,18 +6543,18 @@ function asRecord(input) {
|
|
|
6153
6543
|
}
|
|
6154
6544
|
return void 0;
|
|
6155
6545
|
}
|
|
6156
|
-
function
|
|
6546
|
+
function str2(v) {
|
|
6157
6547
|
return typeof v === "string" && v.trim() ? v : void 0;
|
|
6158
6548
|
}
|
|
6159
6549
|
function toolDetail(name, input) {
|
|
6160
6550
|
if (!input) return void 0;
|
|
6161
|
-
const command =
|
|
6551
|
+
const command = str2(input.command) ?? str2(input.cmd);
|
|
6162
6552
|
if (command) return command;
|
|
6163
|
-
const path =
|
|
6553
|
+
const path = str2(input.file_path) ?? str2(input.path) ?? str2(input.filePath) ?? str2(input.filename);
|
|
6164
6554
|
if (path) return path;
|
|
6165
|
-
const pattern =
|
|
6555
|
+
const pattern = str2(input.pattern) ?? str2(input.glob) ?? str2(input.glob_pattern);
|
|
6166
6556
|
if (pattern) return pattern;
|
|
6167
|
-
const query =
|
|
6557
|
+
const query = str2(input.query) ?? str2(input.prompt);
|
|
6168
6558
|
if (query) return query.length > 80 ? `${query.slice(0, 77)}\u2026` : query;
|
|
6169
6559
|
try {
|
|
6170
6560
|
const raw = JSON.stringify(input);
|
|
@@ -6177,23 +6567,26 @@ function toolDescription(name, input) {
|
|
|
6177
6567
|
const n = name.replace(/^mcp__/, "").replace(/__/g, " \xB7 ");
|
|
6178
6568
|
if (/^get_record_types$|recordTypes/i.test(name)) return "List record types";
|
|
6179
6569
|
if (/connectedAgentRequest/i.test(name)) {
|
|
6180
|
-
return
|
|
6570
|
+
return str2(input?.agent_id) ? `Ask connected agent` : "Ask connected agent";
|
|
6181
6571
|
}
|
|
6182
6572
|
if (/present_artifact$/i.test(name)) {
|
|
6183
|
-
return
|
|
6573
|
+
return str2(input?.title) ? `Present ${str2(input?.title)}` : "Present artifact";
|
|
6574
|
+
}
|
|
6575
|
+
if (/present_plan$/i.test(name)) {
|
|
6576
|
+
return str2(input?.title) ? `Plan ${str2(input?.title)}` : "Present plan";
|
|
6184
6577
|
}
|
|
6185
6578
|
if (/present_schema$/i.test(name)) {
|
|
6186
|
-
return
|
|
6579
|
+
return str2(input?.title) ? `Schema ${str2(input?.title)}` : "Present schema";
|
|
6187
6580
|
}
|
|
6188
6581
|
if (/present_files$/i.test(name)) {
|
|
6189
|
-
return
|
|
6582
|
+
return str2(input?.title) ? `Files ${str2(input?.title)}` : "Present files";
|
|
6190
6583
|
}
|
|
6191
6584
|
if (/^(create|update)_artifact$/i.test(name.replace(/^mcp__[^_]+__/, ""))) {
|
|
6192
|
-
return
|
|
6585
|
+
return str2(input?.title) ? `Artifact ${str2(input?.title)}` : "Artifact";
|
|
6193
6586
|
}
|
|
6194
6587
|
if (!input) return n;
|
|
6195
|
-
if (/bash|shell|terminal/i.test(name) &&
|
|
6196
|
-
const cmd =
|
|
6588
|
+
if (/bash|shell|terminal/i.test(name) && str2(input.command)) {
|
|
6589
|
+
const cmd = str2(input.command);
|
|
6197
6590
|
if (/git\s+fetch/i.test(cmd)) return "Fetch latest from origin and check status";
|
|
6198
6591
|
if (/git\s+status/i.test(cmd)) return "Check git status";
|
|
6199
6592
|
if (/git\s+log/i.test(cmd)) return "Inspect recent commits";
|
|
@@ -6202,11 +6595,11 @@ function toolDescription(name, input) {
|
|
|
6202
6595
|
return "Run shell command";
|
|
6203
6596
|
}
|
|
6204
6597
|
if (/edit|write|apply/i.test(name)) {
|
|
6205
|
-
const path =
|
|
6598
|
+
const path = str2(input.file_path) ?? str2(input.path);
|
|
6206
6599
|
return path ? `Edit ${path.split("/").pop()}` : "Edit file";
|
|
6207
6600
|
}
|
|
6208
6601
|
if (/read/i.test(name)) {
|
|
6209
|
-
const path =
|
|
6602
|
+
const path = str2(input.file_path) ?? str2(input.path);
|
|
6210
6603
|
return path ? `Read ${path.split("/").pop()}` : "Read file";
|
|
6211
6604
|
}
|
|
6212
6605
|
if (/grep/i.test(name)) return "Search files";
|
|
@@ -6215,7 +6608,7 @@ function toolDescription(name, input) {
|
|
|
6215
6608
|
}
|
|
6216
6609
|
function toolFilePath(input) {
|
|
6217
6610
|
if (!input) return void 0;
|
|
6218
|
-
return
|
|
6611
|
+
return str2(input.file_path) ?? str2(input.path) ?? str2(input.filePath) ?? str2(input.filename);
|
|
6219
6612
|
}
|
|
6220
6613
|
function countLines(text) {
|
|
6221
6614
|
if (!text) return 0;
|
|
@@ -6223,8 +6616,8 @@ function countLines(text) {
|
|
|
6223
6616
|
}
|
|
6224
6617
|
function diffFromInput(input) {
|
|
6225
6618
|
if (!input) return {};
|
|
6226
|
-
const oldS =
|
|
6227
|
-
const newS =
|
|
6619
|
+
const oldS = str2(input.old_string) ?? str2(input.oldString);
|
|
6620
|
+
const newS = str2(input.new_string) ?? str2(input.newString) ?? str2(input.content);
|
|
6228
6621
|
if (oldS != null || newS != null) {
|
|
6229
6622
|
return {
|
|
6230
6623
|
additions: countLines(newS),
|
|
@@ -6486,6 +6879,7 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
6486
6879
|
// src/orchestrator/orchestrator.ts
|
|
6487
6880
|
init_agents();
|
|
6488
6881
|
init_worktree();
|
|
6882
|
+
init_stack();
|
|
6489
6883
|
|
|
6490
6884
|
// src/hook/conductor.ts
|
|
6491
6885
|
var import_node_fs13 = require("fs");
|
|
@@ -7121,7 +7515,7 @@ async function requireAgent(agent, opts) {
|
|
|
7121
7515
|
init_worktree();
|
|
7122
7516
|
init_thread_store();
|
|
7123
7517
|
init_workspaces();
|
|
7124
|
-
async function createThread(input,
|
|
7518
|
+
async function createThread(input, _onSetupLine) {
|
|
7125
7519
|
await requireAgent(input.agent);
|
|
7126
7520
|
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
7127
7521
|
if (!(0, import_node_fs18.existsSync)(repoPath)) {
|
|
@@ -7131,10 +7525,10 @@ async function createThread(input, onSetupLine) {
|
|
|
7131
7525
|
let sourceIsFork = false;
|
|
7132
7526
|
let prUrl = null;
|
|
7133
7527
|
if (input.sourceType === "pr") {
|
|
7134
|
-
const
|
|
7135
|
-
if (!Number.isFinite(
|
|
7136
|
-
const pr = await getPr(repoPath,
|
|
7137
|
-
if (!pr) throw new Error(`PR #${
|
|
7528
|
+
const num2 = Number(input.sourceRef.replace(/^#/, ""));
|
|
7529
|
+
if (!Number.isFinite(num2)) throw new Error(`Invalid PR number: ${input.sourceRef}`);
|
|
7530
|
+
const pr = await getPr(repoPath, num2);
|
|
7531
|
+
if (!pr) throw new Error(`PR #${num2} not found`);
|
|
7138
7532
|
sourceIsFork = pr.isCrossRepository;
|
|
7139
7533
|
prUrl = pr.url;
|
|
7140
7534
|
const localFetchBranch = `sideboard-pr-${pr.number}`;
|
|
@@ -7179,22 +7573,6 @@ async function createThread(input, onSetupLine) {
|
|
|
7179
7573
|
});
|
|
7180
7574
|
writeThread(thread);
|
|
7181
7575
|
await ensureWorkspace(repoPath);
|
|
7182
|
-
try {
|
|
7183
|
-
let setup = await runSetupScript(repoPath, worktreePath, onSetupLine);
|
|
7184
|
-
if (!setup.ran) {
|
|
7185
|
-
setup = await runCursorWorktreeSetup(repoPath, worktreePath, onSetupLine);
|
|
7186
|
-
}
|
|
7187
|
-
if (setup.ran && setup.exitCode !== 0 && setup.exitCode !== null) {
|
|
7188
|
-
updateThread(thread.id, {
|
|
7189
|
-
lastError: `Setup exited ${setup.exitCode} (thread is still usable)`
|
|
7190
|
-
});
|
|
7191
|
-
}
|
|
7192
|
-
} catch (err) {
|
|
7193
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
7194
|
-
updateThread(thread.id, {
|
|
7195
|
-
lastError: `Setup failed: ${message}`
|
|
7196
|
-
});
|
|
7197
|
-
}
|
|
7198
7576
|
return readThread(thread.id) ?? thread;
|
|
7199
7577
|
}
|
|
7200
7578
|
async function listLinearIssues(agent, repoPath) {
|
|
@@ -7526,7 +7904,9 @@ function worktreeBindingFrom(from) {
|
|
|
7526
7904
|
sourceIsFork: from.sourceIsFork,
|
|
7527
7905
|
parentThreadId: from.parentThreadId,
|
|
7528
7906
|
prUrl: from.prUrl,
|
|
7529
|
-
prTitle: from.prTitle
|
|
7907
|
+
prTitle: from.prTitle,
|
|
7908
|
+
stackId: from.stackId,
|
|
7909
|
+
stackLayer: from.stackLayer
|
|
7530
7910
|
};
|
|
7531
7911
|
}
|
|
7532
7912
|
function threadsSharingWorktree(worktreePath) {
|
|
@@ -7724,15 +8104,13 @@ File: src/client/frontends/desktop/core/UserData.ts
|
|
|
7724
8104
|
`;
|
|
7725
8105
|
|
|
7726
8106
|
// src/review/request-review.ts
|
|
8107
|
+
init_workspace_scratch();
|
|
7727
8108
|
var REPO_REVIEW_PATH = ".sideboard/review.md";
|
|
7728
8109
|
var REPO_REVIEW_NAME = "review.md";
|
|
7729
|
-
var REVIEW_REQUEST_PATH =
|
|
8110
|
+
var REVIEW_REQUEST_PATH = `${ATTACHMENTS_DIR}/Review request.md`;
|
|
8111
|
+
var LEGACY_REVIEW_REQUEST_PATH = `${LEGACY_ATTACHMENTS_DIR}/Review request.md`;
|
|
7730
8112
|
var REVIEW_REQUEST_NAME = "Review request.md";
|
|
7731
8113
|
var REVIEW_REQUEST_PREFILL = "Review.";
|
|
7732
|
-
var ATTACHMENTS_GITIGNORE = `# Sideboard review / composer attachments (local only)
|
|
7733
|
-
*
|
|
7734
|
-
!.gitignore
|
|
7735
|
-
`;
|
|
7736
8114
|
var LEGACY_REVIEW_TEMPLATE_MARKERS = [
|
|
7737
8115
|
"You are acting as a reviewer for a proposed code change made by another engineer.",
|
|
7738
8116
|
"HOW MANY FINDINGS TO RETURN:"
|
|
@@ -7755,10 +8133,10 @@ function readTextIfPresent(abs) {
|
|
|
7755
8133
|
}
|
|
7756
8134
|
}
|
|
7757
8135
|
function ensureAttachmentsGitignore(worktreePath) {
|
|
7758
|
-
const gitignoreAbs = (0, import_node_path18.join)(worktreePath,
|
|
8136
|
+
const gitignoreAbs = (0, import_node_path18.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
|
|
7759
8137
|
if ((0, import_node_fs19.existsSync)(gitignoreAbs)) return;
|
|
7760
8138
|
(0, import_node_fs19.mkdirSync)((0, import_node_path18.dirname)(gitignoreAbs), { recursive: true });
|
|
7761
|
-
(0, import_node_fs19.writeFileSync)(gitignoreAbs,
|
|
8139
|
+
(0, import_node_fs19.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
|
|
7762
8140
|
}
|
|
7763
8141
|
function resolveReviewGuidelines(worktreePath) {
|
|
7764
8142
|
const repoAbs = (0, import_node_path18.join)(worktreePath, REPO_REVIEW_PATH);
|
|
@@ -7781,6 +8159,16 @@ function resolveReviewGuidelines(worktreePath) {
|
|
|
7781
8159
|
source: "local"
|
|
7782
8160
|
};
|
|
7783
8161
|
}
|
|
8162
|
+
const legacyAbs = (0, import_node_path18.join)(worktreePath, LEGACY_REVIEW_REQUEST_PATH);
|
|
8163
|
+
const legacyContent = readTextIfPresent(legacyAbs);
|
|
8164
|
+
if (legacyContent && !shouldRefreshReviewRequestTemplate(legacyContent)) {
|
|
8165
|
+
return {
|
|
8166
|
+
path: LEGACY_REVIEW_REQUEST_PATH,
|
|
8167
|
+
name: REVIEW_REQUEST_NAME,
|
|
8168
|
+
content: legacyContent,
|
|
8169
|
+
source: "local"
|
|
8170
|
+
};
|
|
8171
|
+
}
|
|
7784
8172
|
ensureAttachmentsGitignore(worktreePath);
|
|
7785
8173
|
(0, import_node_fs19.mkdirSync)((0, import_node_path18.dirname)(localAbs), { recursive: true });
|
|
7786
8174
|
(0, import_node_fs19.writeFileSync)(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
|
|
@@ -8260,16 +8648,305 @@ async function importConductorWorkspaceAsync(workspaceId) {
|
|
|
8260
8648
|
return importConductorWorkspace(workspaceId);
|
|
8261
8649
|
}
|
|
8262
8650
|
|
|
8651
|
+
// src/threads/stack-layers.ts
|
|
8652
|
+
var import_node_fs21 = require("fs");
|
|
8653
|
+
init_run();
|
|
8654
|
+
init_stack();
|
|
8655
|
+
init_worktree();
|
|
8656
|
+
init_thread_store();
|
|
8657
|
+
init_workspaces();
|
|
8658
|
+
function stackIdFrom(stack) {
|
|
8659
|
+
if (stack.stackNumber != null) return `gh-stack-${stack.stackNumber}`;
|
|
8660
|
+
const key = stack.layers.map((l) => l.branchName).join("|");
|
|
8661
|
+
if (!key) return null;
|
|
8662
|
+
return `gh-stack-local-${hashShort(key)}`;
|
|
8663
|
+
}
|
|
8664
|
+
function hashShort(s) {
|
|
8665
|
+
let h = 0;
|
|
8666
|
+
for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) | 0;
|
|
8667
|
+
return Math.abs(h).toString(36);
|
|
8668
|
+
}
|
|
8669
|
+
function requireThread3(idOrRef) {
|
|
8670
|
+
const thread = findThreadByRef(idOrRef) ?? readThread(idOrRef);
|
|
8671
|
+
if (!thread) throw new Error(`Thread not found: ${idOrRef}`);
|
|
8672
|
+
return thread;
|
|
8673
|
+
}
|
|
8674
|
+
function sanitizeSlugPart(name) {
|
|
8675
|
+
return name.trim().replace(/^refs\/heads\//, "").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "layer";
|
|
8676
|
+
}
|
|
8677
|
+
function layerSlug(stackId, layer) {
|
|
8678
|
+
const stackPart = stackId.replace(/^gh-stack-/, "s");
|
|
8679
|
+
return sanitizeSlugPart(`${stackPart}-L${layer.position}-${layer.branchName}`);
|
|
8680
|
+
}
|
|
8681
|
+
function findThreadForStackLayer(repoPath, stackId, layer) {
|
|
8682
|
+
const threads = listThreads({ includeArchived: false }).filter(
|
|
8683
|
+
(t) => t.repoPath === repoPath && t.stackId === stackId
|
|
8684
|
+
);
|
|
8685
|
+
const byLayer = threads.find((t) => t.stackLayer === layer.position);
|
|
8686
|
+
if (byLayer) return byLayer;
|
|
8687
|
+
return threads.find((t) => t.branchName === layer.branchName) ?? listThreads({ includeArchived: false }).find(
|
|
8688
|
+
(t) => t.repoPath === repoPath && t.branchName === layer.branchName
|
|
8689
|
+
) ?? null;
|
|
8690
|
+
}
|
|
8691
|
+
async function openStackLayer(input, _onSetupLine) {
|
|
8692
|
+
await requireAgent(input.agent);
|
|
8693
|
+
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
8694
|
+
const stackId = stackIdFrom(input.stack);
|
|
8695
|
+
if (!stackId) throw new Error("Cannot open stack layer without a stack id");
|
|
8696
|
+
const existing = findThreadForStackLayer(repoPath, stackId, input.layer);
|
|
8697
|
+
if (existing) {
|
|
8698
|
+
const patch = {};
|
|
8699
|
+
if (existing.stackId !== stackId) patch.stackId = stackId;
|
|
8700
|
+
if (existing.stackLayer !== input.layer.position) {
|
|
8701
|
+
patch.stackLayer = input.layer.position;
|
|
8702
|
+
}
|
|
8703
|
+
if (input.layer.prUrl && existing.prUrl !== input.layer.prUrl) {
|
|
8704
|
+
patch.prUrl = input.layer.prUrl;
|
|
8705
|
+
}
|
|
8706
|
+
if (input.layer.title && existing.prTitle !== input.layer.title) {
|
|
8707
|
+
patch.prTitle = input.layer.title;
|
|
8708
|
+
}
|
|
8709
|
+
if (Object.keys(patch).length > 0) updateThread(existing.id, patch);
|
|
8710
|
+
return {
|
|
8711
|
+
thread: readThread(existing.id) ?? existing,
|
|
8712
|
+
createdWorktree: false
|
|
8713
|
+
};
|
|
8714
|
+
}
|
|
8715
|
+
let worktreePath = null;
|
|
8716
|
+
let branchName = input.layer.branchName;
|
|
8717
|
+
let createdWorktree = false;
|
|
8718
|
+
const trees = await listWorktrees(repoPath);
|
|
8719
|
+
const checkedOut = trees.find((w) => w.branch === branchName);
|
|
8720
|
+
if (checkedOut?.path && (0, import_node_fs21.existsSync)(checkedOut.path)) {
|
|
8721
|
+
if (input.reuseExistingWorktree !== false) {
|
|
8722
|
+
worktreePath = checkedOut.path;
|
|
8723
|
+
} else {
|
|
8724
|
+
throw new Error(
|
|
8725
|
+
`Branch ${branchName} is already checked out at ${checkedOut.path}`
|
|
8726
|
+
);
|
|
8727
|
+
}
|
|
8728
|
+
}
|
|
8729
|
+
if (!worktreePath) {
|
|
8730
|
+
const slug = layerSlug(stackId, input.layer);
|
|
8731
|
+
const created = await createExistingBranchWorktree({
|
|
8732
|
+
repoPath,
|
|
8733
|
+
branchName,
|
|
8734
|
+
slug
|
|
8735
|
+
});
|
|
8736
|
+
worktreePath = created.worktreePath;
|
|
8737
|
+
branchName = created.branchName;
|
|
8738
|
+
copyConfiguredFiles(repoPath, worktreePath);
|
|
8739
|
+
createdWorktree = true;
|
|
8740
|
+
}
|
|
8741
|
+
const title = input.layer.title?.trim() || (input.layer.prNumber != null ? `PR #${input.layer.prNumber}` : input.layer.branchName);
|
|
8742
|
+
const thread = createEmptyThread({
|
|
8743
|
+
title,
|
|
8744
|
+
userSetTitle: Boolean(input.layer.title?.trim()),
|
|
8745
|
+
sourceType: input.layer.prNumber != null ? "pr" : "branch",
|
|
8746
|
+
sourceRef: input.layer.prNumber != null ? String(input.layer.prNumber) : input.layer.branchName,
|
|
8747
|
+
branchName,
|
|
8748
|
+
worktreePath,
|
|
8749
|
+
repoPath,
|
|
8750
|
+
agent: input.agent,
|
|
8751
|
+
autonomy: input.autonomy ?? "default",
|
|
8752
|
+
model: input.model ?? null,
|
|
8753
|
+
effort: input.effort ?? "high",
|
|
8754
|
+
fast: Boolean(input.fast),
|
|
8755
|
+
planMode: Boolean(input.planMode),
|
|
8756
|
+
parentThreadId: input.parentThreadId ?? null,
|
|
8757
|
+
status: "idle",
|
|
8758
|
+
prUrl: input.layer.prUrl,
|
|
8759
|
+
prTitle: input.layer.title ?? null,
|
|
8760
|
+
stackId,
|
|
8761
|
+
stackLayer: input.layer.position
|
|
8762
|
+
});
|
|
8763
|
+
writeThread(thread);
|
|
8764
|
+
await ensureWorkspace(repoPath);
|
|
8765
|
+
return { thread: readThread(thread.id) ?? thread, createdWorktree };
|
|
8766
|
+
}
|
|
8767
|
+
async function openPrStackLayers(input, onSetupLine) {
|
|
8768
|
+
const from = requireThread3(input.threadRef);
|
|
8769
|
+
if (!from.worktreePath?.trim() || !from.repoPath?.trim()) {
|
|
8770
|
+
throw new Error("Thread has no worktree");
|
|
8771
|
+
}
|
|
8772
|
+
const stack = await getPrStack(from.worktreePath);
|
|
8773
|
+
if (!stack) throw new Error("Current branch is not part of a GitHub PR stack");
|
|
8774
|
+
const layers = input.layer != null ? stack.layers.filter((l) => l.position === input.layer) : stack.layers;
|
|
8775
|
+
if (!layers.length) {
|
|
8776
|
+
throw new Error(
|
|
8777
|
+
input.layer != null ? `No stack layer at position ${input.layer}` : "Stack has no layers"
|
|
8778
|
+
);
|
|
8779
|
+
}
|
|
8780
|
+
const threads = [];
|
|
8781
|
+
const createdThreadIds = [];
|
|
8782
|
+
for (const layer of layers) {
|
|
8783
|
+
const { thread, createdWorktree } = await openStackLayer(
|
|
8784
|
+
{
|
|
8785
|
+
repoPath: from.repoPath,
|
|
8786
|
+
stack,
|
|
8787
|
+
layer,
|
|
8788
|
+
agent: from.agent,
|
|
8789
|
+
autonomy: from.autonomy,
|
|
8790
|
+
model: from.model,
|
|
8791
|
+
effort: from.effort,
|
|
8792
|
+
fast: from.fast,
|
|
8793
|
+
planMode: from.planMode,
|
|
8794
|
+
parentThreadId: from.id
|
|
8795
|
+
},
|
|
8796
|
+
onSetupLine
|
|
8797
|
+
);
|
|
8798
|
+
threads.push(thread);
|
|
8799
|
+
if (createdWorktree) createdThreadIds.push(thread.id);
|
|
8800
|
+
}
|
|
8801
|
+
const stackId = stackIdFrom(stack);
|
|
8802
|
+
const current = stack.layers[stack.currentIndex];
|
|
8803
|
+
if (stackId) {
|
|
8804
|
+
updateThread(from.id, {
|
|
8805
|
+
stackId,
|
|
8806
|
+
stackLayer: current?.position ?? from.stackLayer
|
|
8807
|
+
});
|
|
8808
|
+
}
|
|
8809
|
+
return { stack, threads, createdThreadIds };
|
|
8810
|
+
}
|
|
8811
|
+
async function addStackLayerFromThread(input, onSetupLine) {
|
|
8812
|
+
const from = requireThread3(input.threadRef);
|
|
8813
|
+
if (!from.worktreePath?.trim() || !from.repoPath?.trim()) {
|
|
8814
|
+
throw new Error("Thread has no worktree");
|
|
8815
|
+
}
|
|
8816
|
+
const status = await detectGhStack(from.worktreePath);
|
|
8817
|
+
if (!status.available) throw new Error(status.reason);
|
|
8818
|
+
await addPrStackLayer(from.worktreePath, input.branchName);
|
|
8819
|
+
const stack = await getPrStack(from.worktreePath);
|
|
8820
|
+
if (!stack) throw new Error("Stack not found after adding layer");
|
|
8821
|
+
const layer = stack.layers.find((l) => l.branchName === input.branchName.trim()) ?? stack.layers[stack.layers.length - 1];
|
|
8822
|
+
if (!layer) throw new Error("New stack layer not found");
|
|
8823
|
+
if (input.title?.trim()) {
|
|
8824
|
+
layer.title = input.title.trim();
|
|
8825
|
+
}
|
|
8826
|
+
const thread = await openStackLayer(
|
|
8827
|
+
{
|
|
8828
|
+
repoPath: from.repoPath,
|
|
8829
|
+
stack,
|
|
8830
|
+
layer,
|
|
8831
|
+
agent: from.agent,
|
|
8832
|
+
autonomy: from.autonomy,
|
|
8833
|
+
model: from.model,
|
|
8834
|
+
effort: from.effort,
|
|
8835
|
+
fast: from.fast,
|
|
8836
|
+
planMode: from.planMode,
|
|
8837
|
+
parentThreadId: from.id
|
|
8838
|
+
},
|
|
8839
|
+
onSetupLine
|
|
8840
|
+
);
|
|
8841
|
+
return { stack, thread: thread.thread, createdWorktree: thread.createdWorktree };
|
|
8842
|
+
}
|
|
8843
|
+
async function initStackFromThread(input, onSetupLine) {
|
|
8844
|
+
const from = requireThread3(input.threadRef);
|
|
8845
|
+
if (!from.worktreePath?.trim() || !from.branchName?.trim() || !from.repoPath?.trim()) {
|
|
8846
|
+
throw new Error("Thread has no worktree/branch");
|
|
8847
|
+
}
|
|
8848
|
+
const status = await detectGhStack(from.worktreePath);
|
|
8849
|
+
if (!status.available) throw new Error(status.reason);
|
|
8850
|
+
const branches = [
|
|
8851
|
+
from.branchName,
|
|
8852
|
+
...(input.additionalBranches ?? []).map((b) => b.trim()).filter(Boolean)
|
|
8853
|
+
];
|
|
8854
|
+
await initPrStack(from.worktreePath, branches, {
|
|
8855
|
+
base: input.base ?? await resolveDefaultBranch(from.repoPath)
|
|
8856
|
+
});
|
|
8857
|
+
return openPrStackLayers({ threadRef: from.id }, onSetupLine);
|
|
8858
|
+
}
|
|
8859
|
+
async function createPrStack(input, onSetupLine) {
|
|
8860
|
+
await requireAgent(input.agent);
|
|
8861
|
+
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
8862
|
+
if (!(0, import_node_fs21.existsSync)(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
|
|
8863
|
+
if (!input.branches.length) throw new Error("At least one branch name required");
|
|
8864
|
+
const status = await detectGhStack(repoPath);
|
|
8865
|
+
if (!status.available) throw new Error(status.reason);
|
|
8866
|
+
const team = allocateTeamSlug(repoPath);
|
|
8867
|
+
const base = input.base ?? await resolveDefaultBranch(repoPath);
|
|
8868
|
+
const bootstrap = await createThreadWorktree({
|
|
8869
|
+
repoPath,
|
|
8870
|
+
sourceRef: base,
|
|
8871
|
+
slug: `${team.slug}-stack-init`
|
|
8872
|
+
});
|
|
8873
|
+
copyConfiguredFiles(repoPath, bootstrap.worktreePath);
|
|
8874
|
+
try {
|
|
8875
|
+
await initPrStack(bootstrap.worktreePath, input.branches, { base });
|
|
8876
|
+
const bottom = input.branches[0];
|
|
8877
|
+
const co = await git(["checkout", bottom], bootstrap.worktreePath, {
|
|
8878
|
+
reject: false
|
|
8879
|
+
});
|
|
8880
|
+
if (co.exitCode !== 0) {
|
|
8881
|
+
throw new Error(
|
|
8882
|
+
co.stderr.trim() || co.stdout.trim() || `Could not check out ${bottom} after stack init`
|
|
8883
|
+
);
|
|
8884
|
+
}
|
|
8885
|
+
} catch (err) {
|
|
8886
|
+
try {
|
|
8887
|
+
await removeWorktree(repoPath, bootstrap.worktreePath, {
|
|
8888
|
+
deleteBranch: bootstrap.branchName
|
|
8889
|
+
});
|
|
8890
|
+
} catch {
|
|
8891
|
+
}
|
|
8892
|
+
throw err;
|
|
8893
|
+
}
|
|
8894
|
+
const stack = await getPrStack(bootstrap.worktreePath);
|
|
8895
|
+
if (!stack) {
|
|
8896
|
+
try {
|
|
8897
|
+
await removeWorktree(repoPath, bootstrap.worktreePath, {
|
|
8898
|
+
deleteBranch: bootstrap.branchName
|
|
8899
|
+
});
|
|
8900
|
+
} catch {
|
|
8901
|
+
}
|
|
8902
|
+
throw new Error("Stack init succeeded but gh stack view returned no stack");
|
|
8903
|
+
}
|
|
8904
|
+
const threads = [];
|
|
8905
|
+
const createdThreadIds = [];
|
|
8906
|
+
for (const layer of stack.layers) {
|
|
8907
|
+
const title = layer.position === 1 && input.title?.trim() ? input.title.trim() : layer.title;
|
|
8908
|
+
const opened = await openStackLayer(
|
|
8909
|
+
{
|
|
8910
|
+
repoPath,
|
|
8911
|
+
stack,
|
|
8912
|
+
layer: title ? { ...layer, title } : layer,
|
|
8913
|
+
agent: input.agent,
|
|
8914
|
+
autonomy: input.autonomy,
|
|
8915
|
+
model: input.model,
|
|
8916
|
+
effort: input.effort,
|
|
8917
|
+
fast: input.fast,
|
|
8918
|
+
planMode: input.planMode,
|
|
8919
|
+
reuseExistingWorktree: true
|
|
8920
|
+
},
|
|
8921
|
+
onSetupLine
|
|
8922
|
+
);
|
|
8923
|
+
threads.push(opened.thread);
|
|
8924
|
+
if (opened.createdWorktree || opened.thread.worktreePath === bootstrap.worktreePath) {
|
|
8925
|
+
createdThreadIds.push(opened.thread.id);
|
|
8926
|
+
}
|
|
8927
|
+
}
|
|
8928
|
+
const claimed = new Set(threads.map((t) => t.worktreePath));
|
|
8929
|
+
if (!claimed.has(bootstrap.worktreePath) && (0, import_node_fs21.existsSync)(bootstrap.worktreePath)) {
|
|
8930
|
+
try {
|
|
8931
|
+
await removeWorktree(repoPath, bootstrap.worktreePath, {
|
|
8932
|
+
deleteBranch: bootstrap.branchName
|
|
8933
|
+
});
|
|
8934
|
+
} catch {
|
|
8935
|
+
}
|
|
8936
|
+
}
|
|
8937
|
+
return { stack, threads, createdThreadIds };
|
|
8938
|
+
}
|
|
8939
|
+
|
|
8263
8940
|
// src/land/land.ts
|
|
8264
8941
|
init_worktree();
|
|
8265
8942
|
|
|
8266
8943
|
// src/diff/diff.ts
|
|
8267
|
-
var
|
|
8944
|
+
var import_node_fs22 = require("fs");
|
|
8268
8945
|
var import_node_path20 = require("path");
|
|
8269
8946
|
init_run();
|
|
8270
8947
|
init_worktree();
|
|
8271
8948
|
async function inspectGitWorktree(worktreePath) {
|
|
8272
|
-
if (!worktreePath || !(0,
|
|
8949
|
+
if (!worktreePath || !(0, import_node_fs22.existsSync)(worktreePath)) return "missing_worktree";
|
|
8273
8950
|
const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
|
|
8274
8951
|
reject: false
|
|
8275
8952
|
});
|
|
@@ -8277,7 +8954,7 @@ async function inspectGitWorktree(worktreePath) {
|
|
|
8277
8954
|
return "ok";
|
|
8278
8955
|
}
|
|
8279
8956
|
async function initializeGitRepository(worktreePath) {
|
|
8280
|
-
if (!worktreePath || !(0,
|
|
8957
|
+
if (!worktreePath || !(0, import_node_fs22.existsSync)(worktreePath)) {
|
|
8281
8958
|
throw new Error("Worktree not found");
|
|
8282
8959
|
}
|
|
8283
8960
|
const status = await inspectGitWorktree(worktreePath);
|
|
@@ -8573,33 +9250,33 @@ async function getDiff(worktreePath, repoPath, opts) {
|
|
|
8573
9250
|
let combinedDiff = "";
|
|
8574
9251
|
let labelBase = base;
|
|
8575
9252
|
if (scope === "staged") {
|
|
8576
|
-
const [ns,
|
|
9253
|
+
const [ns, num2, diff] = await Promise.all([
|
|
8577
9254
|
git(["diff", "--name-status", "--cached"], worktreePath, { reject: false }),
|
|
8578
9255
|
git(["diff", "--numstat", "--cached"], worktreePath, { reject: false }),
|
|
8579
9256
|
git(["diff", "--cached"], worktreePath, { reject: false })
|
|
8580
9257
|
]);
|
|
8581
9258
|
nameStatus = ns.stdout;
|
|
8582
|
-
numstat =
|
|
9259
|
+
numstat = num2.stdout;
|
|
8583
9260
|
combinedDiff = diff.stdout;
|
|
8584
9261
|
labelBase = "staged";
|
|
8585
9262
|
} else if (scope === "unstaged") {
|
|
8586
|
-
const [ns,
|
|
9263
|
+
const [ns, num2, diff] = await Promise.all([
|
|
8587
9264
|
git(["diff", "--name-status"], worktreePath, { reject: false }),
|
|
8588
9265
|
git(["diff", "--numstat"], worktreePath, { reject: false }),
|
|
8589
9266
|
git(["diff"], worktreePath, { reject: false })
|
|
8590
9267
|
]);
|
|
8591
9268
|
nameStatus = ns.stdout;
|
|
8592
|
-
numstat =
|
|
9269
|
+
numstat = num2.stdout;
|
|
8593
9270
|
combinedDiff = diff.stdout;
|
|
8594
9271
|
labelBase = "unstaged";
|
|
8595
9272
|
} else if (scope === "uncommitted") {
|
|
8596
|
-
const [ns,
|
|
9273
|
+
const [ns, num2, diff] = await Promise.all([
|
|
8597
9274
|
git(["diff", "--name-status", "HEAD"], worktreePath, { reject: false }),
|
|
8598
9275
|
git(["diff", "--numstat", "HEAD"], worktreePath, { reject: false }),
|
|
8599
9276
|
git(["diff", "HEAD"], worktreePath, { reject: false })
|
|
8600
9277
|
]);
|
|
8601
9278
|
nameStatus = ns.stdout;
|
|
8602
|
-
numstat =
|
|
9279
|
+
numstat = num2.stdout;
|
|
8603
9280
|
combinedDiff = diff.stdout;
|
|
8604
9281
|
labelBase = "HEAD";
|
|
8605
9282
|
} else if (scope === "last_turn") {
|
|
@@ -8618,34 +9295,34 @@ async function getDiff(worktreePath, repoPath, opts) {
|
|
|
8618
9295
|
scopeStats
|
|
8619
9296
|
};
|
|
8620
9297
|
}
|
|
8621
|
-
const [ns,
|
|
9298
|
+
const [ns, num2, diff] = await Promise.all([
|
|
8622
9299
|
git(["diff", "--name-status", lastTurnBase], worktreePath, { reject: false }),
|
|
8623
9300
|
git(["diff", "--numstat", lastTurnBase], worktreePath, { reject: false }),
|
|
8624
9301
|
git(["diff", lastTurnBase], worktreePath, { reject: false })
|
|
8625
9302
|
]);
|
|
8626
9303
|
nameStatus = ns.stdout;
|
|
8627
|
-
numstat =
|
|
9304
|
+
numstat = num2.stdout;
|
|
8628
9305
|
combinedDiff = diff.stdout;
|
|
8629
9306
|
labelBase = "last turn";
|
|
8630
9307
|
} else if (scope === "commits" && commitSha) {
|
|
8631
9308
|
const range = `${commitSha}^!`;
|
|
8632
|
-
const [ns,
|
|
9309
|
+
const [ns, num2, diff] = await Promise.all([
|
|
8633
9310
|
git(["diff", "--name-status", range], worktreePath, { reject: false }),
|
|
8634
9311
|
git(["diff", "--numstat", range], worktreePath, { reject: false }),
|
|
8635
9312
|
git(["diff", range], worktreePath, { reject: false })
|
|
8636
9313
|
]);
|
|
8637
9314
|
nameStatus = ns.stdout;
|
|
8638
|
-
numstat =
|
|
9315
|
+
numstat = num2.stdout;
|
|
8639
9316
|
combinedDiff = diff.stdout;
|
|
8640
9317
|
labelBase = commitSha.slice(0, 7);
|
|
8641
9318
|
} else {
|
|
8642
|
-
const [ns,
|
|
9319
|
+
const [ns, num2, diff] = await Promise.all([
|
|
8643
9320
|
git(["diff", "--name-status", mergeBase], worktreePath, { reject: false }),
|
|
8644
9321
|
git(["diff", "--numstat", mergeBase], worktreePath, { reject: false }),
|
|
8645
9322
|
git(["diff", mergeBase], worktreePath, { reject: false })
|
|
8646
9323
|
]);
|
|
8647
9324
|
nameStatus = ns.stdout;
|
|
8648
|
-
numstat =
|
|
9325
|
+
numstat = num2.stdout;
|
|
8649
9326
|
combinedDiff = diff.stdout;
|
|
8650
9327
|
labelBase = base;
|
|
8651
9328
|
}
|
|
@@ -8715,7 +9392,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
|
8715
9392
|
assertSafeRelativePath(relativePath);
|
|
8716
9393
|
const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
|
|
8717
9394
|
const abs = (0, import_node_path20.join)(worktreePath, relativePath);
|
|
8718
|
-
const st = (0,
|
|
9395
|
+
const st = (0, import_node_fs22.statSync)(abs);
|
|
8719
9396
|
if (!st.isFile()) {
|
|
8720
9397
|
throw new Error(`Not a file: ${relativePath}`);
|
|
8721
9398
|
}
|
|
@@ -8724,7 +9401,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
|
8724
9401
|
`File too large to upload (${st.size} bytes; max ${maxBytes})`
|
|
8725
9402
|
);
|
|
8726
9403
|
}
|
|
8727
|
-
const buf = (0,
|
|
9404
|
+
const buf = (0, import_node_fs22.readFileSync)(abs);
|
|
8728
9405
|
return {
|
|
8729
9406
|
path: relativePath,
|
|
8730
9407
|
contentBase64: buf.toString("base64"),
|
|
@@ -8735,11 +9412,11 @@ function readWorktreeFile(worktreePath, relativePath, opts) {
|
|
|
8735
9412
|
assertSafeRelativePath(relativePath);
|
|
8736
9413
|
const maxBytes = opts?.maxBytes ?? 2e5;
|
|
8737
9414
|
const abs = (0, import_node_path20.join)(worktreePath, relativePath);
|
|
8738
|
-
const st = (0,
|
|
9415
|
+
const st = (0, import_node_fs22.statSync)(abs);
|
|
8739
9416
|
if (!st.isFile()) {
|
|
8740
9417
|
throw new Error(`Not a file: ${relativePath}`);
|
|
8741
9418
|
}
|
|
8742
|
-
const buf = (0,
|
|
9419
|
+
const buf = (0, import_node_fs22.readFileSync)(abs);
|
|
8743
9420
|
if (isImageRelativePath(relativePath)) {
|
|
8744
9421
|
const maxImageBytes = Math.max(maxBytes, 15e6);
|
|
8745
9422
|
const truncated2 = buf.length > maxImageBytes;
|
|
@@ -8783,8 +9460,8 @@ function assertSafeRelativePath(relativePath) {
|
|
|
8783
9460
|
function writeWorktreeFile(worktreePath, relativePath, content) {
|
|
8784
9461
|
assertSafeRelativePath(relativePath);
|
|
8785
9462
|
const abs = (0, import_node_path20.join)(worktreePath, relativePath);
|
|
8786
|
-
(0,
|
|
8787
|
-
(0,
|
|
9463
|
+
(0, import_node_fs22.mkdirSync)((0, import_node_path20.dirname)(abs), { recursive: true });
|
|
9464
|
+
(0, import_node_fs22.writeFileSync)(abs, content, "utf8");
|
|
8788
9465
|
return { path: relativePath };
|
|
8789
9466
|
}
|
|
8790
9467
|
async function getDiffSummary(worktreePath, repoPath, opts) {
|
|
@@ -8963,7 +9640,7 @@ async function confirmLand(thread, opts) {
|
|
|
8963
9640
|
}
|
|
8964
9641
|
|
|
8965
9642
|
// src/skills/discover.ts
|
|
8966
|
-
var
|
|
9643
|
+
var import_node_fs23 = require("fs");
|
|
8967
9644
|
var import_node_os9 = require("os");
|
|
8968
9645
|
var import_node_path21 = require("path");
|
|
8969
9646
|
function toCommand(name) {
|
|
@@ -8997,7 +9674,7 @@ function parseFrontmatter(content) {
|
|
|
8997
9674
|
}
|
|
8998
9675
|
function readSkill(skillMd, source) {
|
|
8999
9676
|
try {
|
|
9000
|
-
const content = (0,
|
|
9677
|
+
const content = (0, import_node_fs23.readFileSync)(skillMd, "utf8");
|
|
9001
9678
|
const { name: fmName, description } = parseFrontmatter(content);
|
|
9002
9679
|
const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
|
|
9003
9680
|
const name = fmName || dirName;
|
|
@@ -9016,19 +9693,19 @@ function readSkill(skillMd, source) {
|
|
|
9016
9693
|
}
|
|
9017
9694
|
}
|
|
9018
9695
|
function scanSkillsDir(dir, source, out) {
|
|
9019
|
-
if (!(0,
|
|
9696
|
+
if (!(0, import_node_fs23.existsSync)(dir)) return;
|
|
9020
9697
|
let entries;
|
|
9021
9698
|
try {
|
|
9022
|
-
entries = (0,
|
|
9699
|
+
entries = (0, import_node_fs23.readdirSync)(dir);
|
|
9023
9700
|
} catch {
|
|
9024
9701
|
return;
|
|
9025
9702
|
}
|
|
9026
9703
|
for (const entry of entries) {
|
|
9027
9704
|
if (entry.startsWith(".")) continue;
|
|
9028
9705
|
const skillMd = (0, import_node_path21.join)(dir, entry, "SKILL.md");
|
|
9029
|
-
if (!(0,
|
|
9706
|
+
if (!(0, import_node_fs23.existsSync)(skillMd)) continue;
|
|
9030
9707
|
try {
|
|
9031
|
-
if (!(0,
|
|
9708
|
+
if (!(0, import_node_fs23.statSync)(skillMd).isFile()) continue;
|
|
9032
9709
|
} catch {
|
|
9033
9710
|
continue;
|
|
9034
9711
|
}
|
|
@@ -9037,12 +9714,12 @@ function scanSkillsDir(dir, source, out) {
|
|
|
9037
9714
|
}
|
|
9038
9715
|
}
|
|
9039
9716
|
function scanClaudePluginSkills(pluginsRoot, out) {
|
|
9040
|
-
if (!(0,
|
|
9717
|
+
if (!(0, import_node_fs23.existsSync)(pluginsRoot)) return;
|
|
9041
9718
|
const walk = (dir, depth, lookingForSkillsDir) => {
|
|
9042
9719
|
if (depth > 7) return;
|
|
9043
9720
|
let entries;
|
|
9044
9721
|
try {
|
|
9045
|
-
entries = (0,
|
|
9722
|
+
entries = (0, import_node_fs23.readdirSync)(dir);
|
|
9046
9723
|
} catch {
|
|
9047
9724
|
return;
|
|
9048
9725
|
}
|
|
@@ -9054,7 +9731,7 @@ function scanClaudePluginSkills(pluginsRoot, out) {
|
|
|
9054
9731
|
if (entry === "node_modules" || entry === ".git") continue;
|
|
9055
9732
|
const full = (0, import_node_path21.join)(dir, entry);
|
|
9056
9733
|
try {
|
|
9057
|
-
if (!(0,
|
|
9734
|
+
if (!(0, import_node_fs23.statSync)(full).isDirectory()) continue;
|
|
9058
9735
|
} catch {
|
|
9059
9736
|
continue;
|
|
9060
9737
|
}
|
|
@@ -9094,7 +9771,7 @@ function discoverSkills(worktreePath) {
|
|
|
9094
9771
|
return [...byCommand.values()].sort((a, b) => a.command.localeCompare(b.command));
|
|
9095
9772
|
}
|
|
9096
9773
|
function readSkillBody(skillPath, maxChars = 12e3) {
|
|
9097
|
-
const raw = (0,
|
|
9774
|
+
const raw = (0, import_node_fs23.readFileSync)(skillPath, "utf8");
|
|
9098
9775
|
if (raw.startsWith("---")) {
|
|
9099
9776
|
const end = raw.indexOf("\n---", 3);
|
|
9100
9777
|
if (end >= 0) {
|
|
@@ -9187,9 +9864,10 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
|
|
|
9187
9864
|
}
|
|
9188
9865
|
|
|
9189
9866
|
// src/composer/stage-files.ts
|
|
9190
|
-
var
|
|
9867
|
+
var import_node_fs24 = require("fs");
|
|
9191
9868
|
var import_node_path22 = require("path");
|
|
9192
9869
|
var import_node_crypto5 = require("crypto");
|
|
9870
|
+
init_workspace_scratch();
|
|
9193
9871
|
var IMAGE_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
9194
9872
|
"png",
|
|
9195
9873
|
"jpg",
|
|
@@ -9210,11 +9888,6 @@ var IMAGE_MIME_BY_EXT = {
|
|
|
9210
9888
|
bmp: "image/bmp",
|
|
9211
9889
|
ico: "image/x-icon"
|
|
9212
9890
|
};
|
|
9213
|
-
var ATTACHMENTS_DIR = ".sideboard/attachments";
|
|
9214
|
-
var ATTACHMENTS_GITIGNORE2 = `# Sideboard review / composer attachments (local only)
|
|
9215
|
-
*
|
|
9216
|
-
!.gitignore
|
|
9217
|
-
`;
|
|
9218
9891
|
var MAX_INLINE_BYTES = 4e5;
|
|
9219
9892
|
var MAX_PREVIEW_BYTES = 5e6;
|
|
9220
9893
|
function fileExtension(filePath) {
|
|
@@ -9229,21 +9902,21 @@ function imageMimeType(filePath) {
|
|
|
9229
9902
|
}
|
|
9230
9903
|
function ensureAttachmentsDir(worktreePath) {
|
|
9231
9904
|
const dir = (0, import_node_path22.join)(worktreePath, ATTACHMENTS_DIR);
|
|
9232
|
-
(0,
|
|
9905
|
+
(0, import_node_fs24.mkdirSync)(dir, { recursive: true });
|
|
9233
9906
|
const gi = (0, import_node_path22.join)(dir, ".gitignore");
|
|
9234
|
-
if (!(0,
|
|
9235
|
-
(0,
|
|
9907
|
+
if (!(0, import_node_fs24.existsSync)(gi)) {
|
|
9908
|
+
(0, import_node_fs24.writeFileSync)(gi, attachmentsGitignoreBody(), "utf8");
|
|
9236
9909
|
}
|
|
9237
9910
|
return dir;
|
|
9238
9911
|
}
|
|
9239
9912
|
function uniqueAttachmentName(dir, originalName) {
|
|
9240
9913
|
const safe = originalName.replace(/[/\\]/g, "_") || "file";
|
|
9241
|
-
if (!(0,
|
|
9914
|
+
if (!(0, import_node_fs24.existsSync)((0, import_node_path22.join)(dir, safe))) return safe;
|
|
9242
9915
|
const ext = (0, import_node_path22.extname)(safe);
|
|
9243
9916
|
const stem = ext ? safe.slice(0, -ext.length) : safe;
|
|
9244
9917
|
for (let i = 1; i < 1e4; i++) {
|
|
9245
9918
|
const candidate = `${stem}-${i}${ext}`;
|
|
9246
|
-
if (!(0,
|
|
9919
|
+
if (!(0, import_node_fs24.existsSync)((0, import_node_path22.join)(dir, candidate))) return candidate;
|
|
9247
9920
|
}
|
|
9248
9921
|
return `${stem}-${(0, import_node_crypto5.randomUUID)()}${ext}`;
|
|
9249
9922
|
}
|
|
@@ -9301,13 +9974,13 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
|
|
|
9301
9974
|
for (const abs of absolutePaths) {
|
|
9302
9975
|
const originalName = (0, import_node_path22.basename)(abs);
|
|
9303
9976
|
try {
|
|
9304
|
-
const st = (0,
|
|
9977
|
+
const st = (0, import_node_fs24.statSync)(abs);
|
|
9305
9978
|
if (!st.isFile()) continue;
|
|
9306
9979
|
const name = uniqueAttachmentName(dir, originalName);
|
|
9307
9980
|
const destAbs = (0, import_node_path22.join)(dir, name);
|
|
9308
|
-
(0,
|
|
9981
|
+
(0, import_node_fs24.copyFileSync)(abs, destAbs);
|
|
9309
9982
|
const rel = `${ATTACHMENTS_DIR}/${name}`;
|
|
9310
|
-
const buf = (0,
|
|
9983
|
+
const buf = (0, import_node_fs24.readFileSync)(destAbs);
|
|
9311
9984
|
out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
|
|
9312
9985
|
} catch (err) {
|
|
9313
9986
|
out.push({
|
|
@@ -9330,7 +10003,7 @@ function stageBuffersAsAttachments(worktreePath, buffers) {
|
|
|
9330
10003
|
const buf = Buffer.from(item.dataBase64, "base64");
|
|
9331
10004
|
const name = uniqueAttachmentName(dir, originalName);
|
|
9332
10005
|
const destAbs = (0, import_node_path22.join)(dir, name);
|
|
9333
|
-
(0,
|
|
10006
|
+
(0, import_node_fs24.writeFileSync)(destAbs, buf);
|
|
9334
10007
|
const rel = `${ATTACHMENTS_DIR}/${name}`;
|
|
9335
10008
|
out.push(attachmentFromBuffer(name, buf, { path: rel }));
|
|
9336
10009
|
} catch (err) {
|
|
@@ -9359,9 +10032,9 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
|
|
|
9359
10032
|
const name = (0, import_node_path22.basename)(rel);
|
|
9360
10033
|
try {
|
|
9361
10034
|
const abs = (0, import_node_path22.join)(worktreePath, rel);
|
|
9362
|
-
const st = (0,
|
|
10035
|
+
const st = (0, import_node_fs24.statSync)(abs);
|
|
9363
10036
|
if (!st.isFile()) continue;
|
|
9364
|
-
const buf = (0,
|
|
10037
|
+
const buf = (0, import_node_fs24.readFileSync)(abs);
|
|
9365
10038
|
out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
|
|
9366
10039
|
} catch (err) {
|
|
9367
10040
|
out.push({
|
|
@@ -9376,7 +10049,7 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
|
|
|
9376
10049
|
}
|
|
9377
10050
|
|
|
9378
10051
|
// src/agents/instructions.ts
|
|
9379
|
-
var
|
|
10052
|
+
var import_node_fs25 = require("fs");
|
|
9380
10053
|
var import_node_path23 = require("path");
|
|
9381
10054
|
init_worktree_labels();
|
|
9382
10055
|
function normPath2(p) {
|
|
@@ -9517,10 +10190,10 @@ function loadAgentInstructions(worktreePath, agent) {
|
|
|
9517
10190
|
for (const rel of candidates) {
|
|
9518
10191
|
if (seen.has(rel)) continue;
|
|
9519
10192
|
const abs = (0, import_node_path23.join)(worktreePath, rel);
|
|
9520
|
-
if (!(0,
|
|
10193
|
+
if (!(0, import_node_fs25.existsSync)(abs)) continue;
|
|
9521
10194
|
try {
|
|
9522
|
-
if (!(0,
|
|
9523
|
-
let content = (0,
|
|
10195
|
+
if (!(0, import_node_fs25.statSync)(abs).isFile()) continue;
|
|
10196
|
+
let content = (0, import_node_fs25.readFileSync)(abs, "utf8");
|
|
9524
10197
|
if (!content.trim()) continue;
|
|
9525
10198
|
if (content.length > MAX_CHARS_PER_FILE) {
|
|
9526
10199
|
content = `${content.slice(0, MAX_CHARS_PER_FILE)}
|
|
@@ -9553,6 +10226,7 @@ function formatAgentInstructions(files) {
|
|
|
9553
10226
|
|
|
9554
10227
|
// src/orchestrator/orchestrator.ts
|
|
9555
10228
|
init_types();
|
|
10229
|
+
init_plan_file();
|
|
9556
10230
|
init_settings();
|
|
9557
10231
|
|
|
9558
10232
|
// src/threads/sync-branch.ts
|
|
@@ -9672,7 +10346,7 @@ var Orchestrator = class {
|
|
|
9672
10346
|
}
|
|
9673
10347
|
continue;
|
|
9674
10348
|
}
|
|
9675
|
-
if (!(0,
|
|
10349
|
+
if (!(0, import_node_fs28.existsSync)(thread.worktreePath)) {
|
|
9676
10350
|
setStatus(thread.id, "broken", "Worktree missing on disk");
|
|
9677
10351
|
this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
|
|
9678
10352
|
continue;
|
|
@@ -9817,26 +10491,43 @@ var Orchestrator = class {
|
|
|
9817
10491
|
return findThreadByRef(idOrRef) ?? readThread(idOrRef);
|
|
9818
10492
|
}
|
|
9819
10493
|
async createThread(input) {
|
|
9820
|
-
|
|
9821
|
-
this.emit({
|
|
9822
|
-
type: "turn_output",
|
|
9823
|
-
threadId: "pending",
|
|
9824
|
-
event: { type: "stdout", data: line }
|
|
9825
|
-
});
|
|
9826
|
-
});
|
|
10494
|
+
const thread = await createThread(input);
|
|
9827
10495
|
this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
|
|
10496
|
+
void this.finishCreateThread(thread.id, input.prompt?.trim() || void 0);
|
|
10497
|
+
return thread;
|
|
10498
|
+
}
|
|
10499
|
+
async finishCreateThread(threadId, prompt) {
|
|
10500
|
+
await this.runSetupAfterCreate(threadId);
|
|
9828
10501
|
const { autoRunAfterSetupEnabled: autoRunAfterSetupEnabled2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
|
|
9829
10502
|
if (autoRunAfterSetupEnabled2()) {
|
|
9830
10503
|
try {
|
|
9831
|
-
await this.startDev(
|
|
10504
|
+
await this.startDev(threadId);
|
|
9832
10505
|
} catch {
|
|
9833
10506
|
}
|
|
9834
10507
|
}
|
|
9835
|
-
const prompt = input.prompt?.trim();
|
|
9836
10508
|
if (prompt) {
|
|
9837
|
-
|
|
10509
|
+
try {
|
|
10510
|
+
await this.send(threadId, prompt);
|
|
10511
|
+
} catch (err) {
|
|
10512
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10513
|
+
updateThread(threadId, {
|
|
10514
|
+
lastError: `First prompt failed: ${message}`
|
|
10515
|
+
});
|
|
10516
|
+
}
|
|
10517
|
+
}
|
|
10518
|
+
}
|
|
10519
|
+
/** Run workspace setup after a new worktree is created (no-op if none configured). */
|
|
10520
|
+
async runSetupAfterCreate(threadId) {
|
|
10521
|
+
try {
|
|
10522
|
+
await this.runSetup(threadId);
|
|
10523
|
+
} catch (err) {
|
|
10524
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10525
|
+
if (/no setup script/i.test(message)) return;
|
|
10526
|
+
if (/already running/i.test(message)) return;
|
|
10527
|
+
updateThread(threadId, {
|
|
10528
|
+
lastError: `Setup failed: ${message}`
|
|
10529
|
+
});
|
|
9838
10530
|
}
|
|
9839
|
-
return thread;
|
|
9840
10531
|
}
|
|
9841
10532
|
listWorkspaces() {
|
|
9842
10533
|
const fromThreads = listThreads({ includeArchived: false }).map((t) => t.repoPath);
|
|
@@ -10169,8 +10860,73 @@ var Orchestrator = class {
|
|
|
10169
10860
|
}
|
|
10170
10861
|
}
|
|
10171
10862
|
}
|
|
10172
|
-
|
|
10173
|
-
|
|
10863
|
+
let lastStderr = summarizeTurnStderr(stderrTail);
|
|
10864
|
+
let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
|
|
10865
|
+
if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && looksLikeInvalidAgentSession(detail) && this.requireThread(threadId).sessionId && this.requireThread(threadId).agent !== "cursor" && this.requireThread(threadId).agent !== "brightsy") {
|
|
10866
|
+
updateThread(threadId, { sessionId: null });
|
|
10867
|
+
pushTurnStderr(
|
|
10868
|
+
stderrTail,
|
|
10869
|
+
"Agent session missing \u2014 starting a fresh session"
|
|
10870
|
+
);
|
|
10871
|
+
this.emit({
|
|
10872
|
+
type: "turn_output",
|
|
10873
|
+
threadId,
|
|
10874
|
+
event: {
|
|
10875
|
+
type: "stderr",
|
|
10876
|
+
data: "Agent session missing \u2014 starting a fresh session"
|
|
10877
|
+
}
|
|
10878
|
+
});
|
|
10879
|
+
const retryThread = this.requireThread(threadId);
|
|
10880
|
+
const prior = retryThread.messages.slice(0, -1);
|
|
10881
|
+
const retrySeed = buildSessionSeed(prior);
|
|
10882
|
+
const retryInstructions = retryThread.agent === "claude" ? null : formatAgentInstructions(
|
|
10883
|
+
loadAgentInstructions(retryThread.worktreePath, retryThread.agent)
|
|
10884
|
+
);
|
|
10885
|
+
const retryPrefix = [
|
|
10886
|
+
coordinatorDirective,
|
|
10887
|
+
worktreeDirective,
|
|
10888
|
+
artifactDirective,
|
|
10889
|
+
renameBranchDirective,
|
|
10890
|
+
retryInstructions,
|
|
10891
|
+
retrySeed
|
|
10892
|
+
].filter(Boolean).join("\n\n---\n\n");
|
|
10893
|
+
const retryHandle = await spawnAgentTurn(
|
|
10894
|
+
retryThread,
|
|
10895
|
+
{ cachedPrefix: retryPrefix, prompt: agentPrompt },
|
|
10896
|
+
(event) => {
|
|
10897
|
+
this.emit({ type: "turn_output", threadId, event });
|
|
10898
|
+
if (event.type === "session_id") {
|
|
10899
|
+
updateThread(threadId, { sessionId: event.data });
|
|
10900
|
+
}
|
|
10901
|
+
if (event.type === "stderr" && typeof event.data === "string") {
|
|
10902
|
+
pushTurnStderr(stderrTail, event.data);
|
|
10903
|
+
}
|
|
10904
|
+
}
|
|
10905
|
+
);
|
|
10906
|
+
this.activeTurns.set(threadId, retryHandle);
|
|
10907
|
+
if (typeof retryHandle.pid === "number" && retryHandle.pid > 0) {
|
|
10908
|
+
updateThread(threadId, { agentPid: retryHandle.pid });
|
|
10909
|
+
}
|
|
10910
|
+
this.processes.set(`${threadId}:agent`, {
|
|
10911
|
+
kind: "agent",
|
|
10912
|
+
pid: retryHandle.pid,
|
|
10913
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10914
|
+
kill: retryHandle.kill
|
|
10915
|
+
});
|
|
10916
|
+
if (this.stoppedTurns.has(threadId)) {
|
|
10917
|
+
retryHandle.kill();
|
|
10918
|
+
}
|
|
10919
|
+
const retryResult = await retryHandle.done;
|
|
10920
|
+
if (retryResult.sessionId) {
|
|
10921
|
+
updateThread(threadId, { sessionId: retryResult.sessionId });
|
|
10922
|
+
}
|
|
10923
|
+
assistantText = retryResult.assistantText.trim();
|
|
10924
|
+
parts = retryResult.parts;
|
|
10925
|
+
usage = retryResult.usage ?? void 0;
|
|
10926
|
+
exitCode = retryResult.exitCode;
|
|
10927
|
+
lastStderr = summarizeTurnStderr(stderrTail);
|
|
10928
|
+
detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
|
|
10929
|
+
}
|
|
10174
10930
|
let chatText = assistantText;
|
|
10175
10931
|
if (exitCode !== 0 && !chatText && looksLikeAgentFailureMessage(detail)) {
|
|
10176
10932
|
chatText = humanizeAgentFailDetail(detail);
|
|
@@ -10186,6 +10942,19 @@ var Orchestrator = class {
|
|
|
10186
10942
|
});
|
|
10187
10943
|
}
|
|
10188
10944
|
const afterTurn = this.requireThread(threadId);
|
|
10945
|
+
if (afterTurn.planMode && afterTurn.worktreePath?.trim()) {
|
|
10946
|
+
const presented = extractPresentedPlan(parts);
|
|
10947
|
+
const exited = parts.some(
|
|
10948
|
+
(p) => p.type === "tool" && /exitplanmode/i.test(p.name)
|
|
10949
|
+
);
|
|
10950
|
+
if (presented?.content) {
|
|
10951
|
+
writePlanFile(afterTurn.worktreePath, presented.content);
|
|
10952
|
+
} else if (exited || chatText && chatText.trim().length >= 400) {
|
|
10953
|
+
if (!readPlanFile(afterTurn.worktreePath) && chatText?.trim()) {
|
|
10954
|
+
writePlanFile(afterTurn.worktreePath, chatText.trim());
|
|
10955
|
+
}
|
|
10956
|
+
}
|
|
10957
|
+
}
|
|
10189
10958
|
if (afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
|
|
10190
10959
|
(p) => p.type === "tool" && /exitplanmode/i.test(p.name)
|
|
10191
10960
|
)) {
|
|
@@ -10667,6 +11436,82 @@ var Orchestrator = class {
|
|
|
10667
11436
|
}
|
|
10668
11437
|
return meta;
|
|
10669
11438
|
}
|
|
11439
|
+
async getPrStack(threadRef) {
|
|
11440
|
+
const thread = this.requireThread(threadRef);
|
|
11441
|
+
if (!thread.worktreePath?.trim()) return null;
|
|
11442
|
+
const stack = await getPrStack(thread.worktreePath);
|
|
11443
|
+
if (!stack) return null;
|
|
11444
|
+
const current = stack.currentIndex >= 0 ? stack.layers[stack.currentIndex] : null;
|
|
11445
|
+
const patch = {};
|
|
11446
|
+
if (stack.stackNumber != null) {
|
|
11447
|
+
const id = `gh-stack-${stack.stackNumber}`;
|
|
11448
|
+
if (thread.stackId !== id) patch.stackId = id;
|
|
11449
|
+
}
|
|
11450
|
+
if (current?.position != null && thread.stackLayer !== current.position) {
|
|
11451
|
+
patch.stackLayer = current.position;
|
|
11452
|
+
}
|
|
11453
|
+
if (current?.prUrl && current.prUrl !== thread.prUrl) patch.prUrl = current.prUrl;
|
|
11454
|
+
if (current?.title && current.title !== thread.prTitle) patch.prTitle = current.title;
|
|
11455
|
+
if (current?.branchName && current.branchName !== thread.branchName) {
|
|
11456
|
+
patch.branchName = current.branchName;
|
|
11457
|
+
}
|
|
11458
|
+
if (Object.keys(patch).length > 0) updateThread(thread.id, patch);
|
|
11459
|
+
return stack;
|
|
11460
|
+
}
|
|
11461
|
+
/** Open worktrees for all (or one) stack layers discovered from a thread. */
|
|
11462
|
+
async openPrStackLayers(threadRef, opts) {
|
|
11463
|
+
const result = await openPrStackLayers({ threadRef, layer: opts?.layer });
|
|
11464
|
+
for (const t of result.threads) {
|
|
11465
|
+
this.emit({ type: "status_changed", threadId: t.id, status: t.status });
|
|
11466
|
+
}
|
|
11467
|
+
for (const id of result.createdThreadIds) {
|
|
11468
|
+
await this.runSetupAfterCreate(id);
|
|
11469
|
+
}
|
|
11470
|
+
return { stack: result.stack, threads: result.threads };
|
|
11471
|
+
}
|
|
11472
|
+
/** Add a branch on top of the thread's stack and open its worktree. */
|
|
11473
|
+
async addStackLayer(threadRef, branchName, opts) {
|
|
11474
|
+
const result = await addStackLayerFromThread({
|
|
11475
|
+
threadRef,
|
|
11476
|
+
branchName,
|
|
11477
|
+
title: opts?.title
|
|
11478
|
+
});
|
|
11479
|
+
this.emit({
|
|
11480
|
+
type: "status_changed",
|
|
11481
|
+
threadId: result.thread.id,
|
|
11482
|
+
status: result.thread.status
|
|
11483
|
+
});
|
|
11484
|
+
if (result.createdWorktree) {
|
|
11485
|
+
await this.runSetupAfterCreate(result.thread.id);
|
|
11486
|
+
}
|
|
11487
|
+
return { stack: result.stack, thread: result.thread };
|
|
11488
|
+
}
|
|
11489
|
+
/** Initialize a stack from the current thread branch (optional extra layers). */
|
|
11490
|
+
async initStackFromThread(threadRef, opts) {
|
|
11491
|
+
const result = await initStackFromThread({
|
|
11492
|
+
threadRef,
|
|
11493
|
+
additionalBranches: opts?.additionalBranches,
|
|
11494
|
+
base: opts?.base
|
|
11495
|
+
});
|
|
11496
|
+
for (const t of result.threads) {
|
|
11497
|
+
this.emit({ type: "status_changed", threadId: t.id, status: t.status });
|
|
11498
|
+
}
|
|
11499
|
+
for (const id of result.createdThreadIds) {
|
|
11500
|
+
await this.runSetupAfterCreate(id);
|
|
11501
|
+
}
|
|
11502
|
+
return { stack: result.stack, threads: result.threads };
|
|
11503
|
+
}
|
|
11504
|
+
/** Create a new multi-layer stack with one worktree per layer. */
|
|
11505
|
+
async createPrStack(input) {
|
|
11506
|
+
const result = await createPrStack(input);
|
|
11507
|
+
for (const t of result.threads) {
|
|
11508
|
+
this.emit({ type: "status_changed", threadId: t.id, status: t.status });
|
|
11509
|
+
}
|
|
11510
|
+
for (const id of result.createdThreadIds) {
|
|
11511
|
+
await this.runSetupAfterCreate(id);
|
|
11512
|
+
}
|
|
11513
|
+
return { stack: result.stack, threads: result.threads };
|
|
11514
|
+
}
|
|
10670
11515
|
async getPrDetails(threadRef) {
|
|
10671
11516
|
const { thread, selector, cwd } = await this.withPrSelector(threadRef);
|
|
10672
11517
|
if (!selector) return null;
|
|
@@ -10729,14 +11574,9 @@ var Orchestrator = class {
|
|
|
10729
11574
|
return forkChatTab(input);
|
|
10730
11575
|
}
|
|
10731
11576
|
async forkThreadWorktree(input) {
|
|
10732
|
-
const thread = await forkThreadWorktree(input
|
|
10733
|
-
this.emit({
|
|
10734
|
-
type: "turn_output",
|
|
10735
|
-
threadId: "pending",
|
|
10736
|
-
event: { type: "stdout", data: line }
|
|
10737
|
-
});
|
|
10738
|
-
});
|
|
11577
|
+
const thread = await forkThreadWorktree(input);
|
|
10739
11578
|
this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
|
|
11579
|
+
await this.runSetupAfterCreate(thread.id);
|
|
10740
11580
|
return thread;
|
|
10741
11581
|
}
|
|
10742
11582
|
renameThread(threadRef, title) {
|
|
@@ -10750,7 +11590,7 @@ var Orchestrator = class {
|
|
|
10750
11590
|
}
|
|
10751
11591
|
/**
|
|
10752
11592
|
* Stage OS / worktree files into composer attachments (copies external files
|
|
10753
|
-
* into `.
|
|
11593
|
+
* into `.context/attachments/` so agents can Read images and binaries).
|
|
10754
11594
|
*/
|
|
10755
11595
|
attachComposerFiles(threadRef, opts) {
|
|
10756
11596
|
const thread = this.requireThread(threadRef);
|
|
@@ -10824,7 +11664,7 @@ var Orchestrator = class {
|
|
|
10824
11664
|
updateThread(thread.id, { worktreePath: globalAgentCwd2() });
|
|
10825
11665
|
return setStatus(thread.id, "idle");
|
|
10826
11666
|
}
|
|
10827
|
-
if (!(0,
|
|
11667
|
+
if (!(0, import_node_fs28.existsSync)(thread.worktreePath)) {
|
|
10828
11668
|
const { createThreadWorktree: createThreadWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
|
|
10829
11669
|
const { execa: execa7 } = await import("execa");
|
|
10830
11670
|
const slug = thread.worktreePath.split("/").pop();
|
|
@@ -11043,7 +11883,7 @@ async function startMcpServer() {
|
|
|
11043
11883
|
async () => {
|
|
11044
11884
|
const threads = orch.getThreads(true);
|
|
11045
11885
|
const lines = threads.map((t) => {
|
|
11046
|
-
const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0,
|
|
11886
|
+
const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path26.basename)(t.repoPath) || t.repoPath;
|
|
11047
11887
|
return `${t.id.slice(0, 8)} ${t.status.padEnd(9)} ${t.agent.padEnd(8)} ${repo} ${t.sourceType}:${t.sourceRef} ${t.title} sideboard://thread/${t.id}${t.devPort ? ` http://localhost:${t.devPort}` : ""}`;
|
|
11048
11888
|
});
|
|
11049
11889
|
return {
|
|
@@ -11105,6 +11945,59 @@ async function startMcpServer() {
|
|
|
11105
11945
|
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
11106
11946
|
}
|
|
11107
11947
|
);
|
|
11948
|
+
server.tool(
|
|
11949
|
+
"ask_user",
|
|
11950
|
+
"Ask the user clarifying multiple-choice questions in Sideboard\u2019s composer (plan mode). Before calling, write a short chat message explaining the decision and what each option means. Include a description on every option. Use for approach forks and requirements \u2014 not for \u201Cis the plan ready?\u201D. After calling, stop and wait for their next message with answers.",
|
|
11951
|
+
{
|
|
11952
|
+
questions: import_zod.z.array(
|
|
11953
|
+
import_zod.z.object({
|
|
11954
|
+
question: import_zod.z.string().describe("Full question text ending with ?"),
|
|
11955
|
+
header: import_zod.z.string().max(24).optional().describe("Short label shown above the question"),
|
|
11956
|
+
multiSelect: import_zod.z.boolean().optional().describe("Allow selecting multiple options"),
|
|
11957
|
+
options: import_zod.z.array(
|
|
11958
|
+
import_zod.z.object({
|
|
11959
|
+
label: import_zod.z.string(),
|
|
11960
|
+
description: import_zod.z.string().optional().describe("What this option means / when to choose it (strongly preferred)")
|
|
11961
|
+
})
|
|
11962
|
+
).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
|
|
11963
|
+
})
|
|
11964
|
+
).min(1).max(4).describe("1\u20134 questions")
|
|
11965
|
+
},
|
|
11966
|
+
async ({ questions }) => {
|
|
11967
|
+
const payload = {
|
|
11968
|
+
ok: true,
|
|
11969
|
+
questions,
|
|
11970
|
+
message: "Questions shown in Sideboard\u2019s composer. Wait for the user\u2019s next message with their answers before continuing."
|
|
11971
|
+
};
|
|
11972
|
+
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
11973
|
+
}
|
|
11974
|
+
);
|
|
11975
|
+
server.tool(
|
|
11976
|
+
"present_plan",
|
|
11977
|
+
"Save the implementation plan as markdown to .context/attachments/plan.md and show it in Sideboard chat for user approval (Copy / Hand off / Approve). Call this when the plan is ready \u2014 required in plan mode. Pass the full plan body in content. Then Claude should call ExitPlanMode.",
|
|
11978
|
+
{
|
|
11979
|
+
title: import_zod.z.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
11980
|
+
content: import_zod.z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
11981
|
+
thread_id: import_zod.z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
11982
|
+
},
|
|
11983
|
+
async ({ title, content, thread_id }) => {
|
|
11984
|
+
const { writePlanFile: writePlanFile2 } = await Promise.resolve().then(() => (init_plan_file(), plan_file_exports));
|
|
11985
|
+
let root = process.cwd();
|
|
11986
|
+
if (thread_id?.trim()) {
|
|
11987
|
+
const t = orch.getThread(thread_id.trim());
|
|
11988
|
+
if (t?.worktreePath?.trim()) root = t.worktreePath;
|
|
11989
|
+
}
|
|
11990
|
+
const path = writePlanFile2(root, content);
|
|
11991
|
+
const payload = {
|
|
11992
|
+
ok: true,
|
|
11993
|
+
path,
|
|
11994
|
+
title: title?.trim() || "Plan",
|
|
11995
|
+
content,
|
|
11996
|
+
message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
|
|
11997
|
+
};
|
|
11998
|
+
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
11999
|
+
}
|
|
12000
|
+
);
|
|
11108
12001
|
server.tool(
|
|
11109
12002
|
"present_schema",
|
|
11110
12003
|
"Open Sideboard\u2019s schema-driven CMS side column (filterable table and/or form). Pass JSON Schema + schemaUi (Brightsy extensions supported). Use datasource=brightsy with resource_id (record type UUID) when logged into Brightsy; use datasource=inline with embedded resource/records for any other source.",
|
|
@@ -11683,6 +12576,126 @@ async function startMcpServer() {
|
|
|
11683
12576
|
};
|
|
11684
12577
|
}
|
|
11685
12578
|
);
|
|
12579
|
+
server.tool(
|
|
12580
|
+
"get_pr_stack",
|
|
12581
|
+
"Load the GitHub PR stack for a thread worktree (`gh stack view --json`). Returns null JSON when the branch is not stacked. Prefer this before mergePr on stacked PRs.",
|
|
12582
|
+
{ ref: import_zod.z.string() },
|
|
12583
|
+
async ({ ref }) => {
|
|
12584
|
+
const stack = await orch.getPrStack(ref);
|
|
12585
|
+
return {
|
|
12586
|
+
content: [{ type: "text", text: JSON.stringify(stack, null, 2) }]
|
|
12587
|
+
};
|
|
12588
|
+
}
|
|
12589
|
+
);
|
|
12590
|
+
server.tool(
|
|
12591
|
+
"open_pr_stack_layers",
|
|
12592
|
+
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
12593
|
+
{
|
|
12594
|
+
ref: import_zod.z.string(),
|
|
12595
|
+
layer: import_zod.z.number().int().positive().optional()
|
|
12596
|
+
},
|
|
12597
|
+
async ({ ref, layer }) => {
|
|
12598
|
+
const result = await orch.openPrStackLayers(ref, { layer });
|
|
12599
|
+
return {
|
|
12600
|
+
content: [
|
|
12601
|
+
{
|
|
12602
|
+
type: "text",
|
|
12603
|
+
text: JSON.stringify(
|
|
12604
|
+
{
|
|
12605
|
+
stackNumber: result.stack.stackNumber,
|
|
12606
|
+
trunk: result.stack.trunk,
|
|
12607
|
+
threads: result.threads.map((t) => ({
|
|
12608
|
+
id: t.id,
|
|
12609
|
+
title: t.title,
|
|
12610
|
+
branchName: t.branchName,
|
|
12611
|
+
stackLayer: t.stackLayer,
|
|
12612
|
+
worktreePath: t.worktreePath,
|
|
12613
|
+
prUrl: t.prUrl,
|
|
12614
|
+
link: `sideboard://thread/${t.id}`
|
|
12615
|
+
}))
|
|
12616
|
+
},
|
|
12617
|
+
null,
|
|
12618
|
+
2
|
|
12619
|
+
)
|
|
12620
|
+
}
|
|
12621
|
+
]
|
|
12622
|
+
};
|
|
12623
|
+
}
|
|
12624
|
+
);
|
|
12625
|
+
server.tool(
|
|
12626
|
+
"add_stack_layer",
|
|
12627
|
+
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
12628
|
+
{
|
|
12629
|
+
ref: import_zod.z.string(),
|
|
12630
|
+
branchName: import_zod.z.string(),
|
|
12631
|
+
title: import_zod.z.string().optional()
|
|
12632
|
+
},
|
|
12633
|
+
async ({ ref, branchName, title }) => {
|
|
12634
|
+
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
12635
|
+
return {
|
|
12636
|
+
content: [
|
|
12637
|
+
{
|
|
12638
|
+
type: "text",
|
|
12639
|
+
text: JSON.stringify(
|
|
12640
|
+
{
|
|
12641
|
+
id: result.thread.id,
|
|
12642
|
+
title: result.thread.title,
|
|
12643
|
+
branchName: result.thread.branchName,
|
|
12644
|
+
stackLayer: result.thread.stackLayer,
|
|
12645
|
+
worktreePath: result.thread.worktreePath,
|
|
12646
|
+
link: `sideboard://thread/${result.thread.id}`
|
|
12647
|
+
},
|
|
12648
|
+
null,
|
|
12649
|
+
2
|
|
12650
|
+
)
|
|
12651
|
+
}
|
|
12652
|
+
]
|
|
12653
|
+
};
|
|
12654
|
+
}
|
|
12655
|
+
);
|
|
12656
|
+
server.tool(
|
|
12657
|
+
"create_pr_stack",
|
|
12658
|
+
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
12659
|
+
{
|
|
12660
|
+
repoPath: import_zod.z.string(),
|
|
12661
|
+
branches: import_zod.z.array(import_zod.z.string()).min(1),
|
|
12662
|
+
agent: import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
|
|
12663
|
+
base: import_zod.z.string().optional(),
|
|
12664
|
+
title: import_zod.z.string().optional()
|
|
12665
|
+
},
|
|
12666
|
+
async (args) => {
|
|
12667
|
+
const result = await orch.createPrStack({
|
|
12668
|
+
repoPath: args.repoPath,
|
|
12669
|
+
branches: args.branches,
|
|
12670
|
+
agent: args.agent,
|
|
12671
|
+
base: args.base,
|
|
12672
|
+
title: args.title
|
|
12673
|
+
});
|
|
12674
|
+
return {
|
|
12675
|
+
content: [
|
|
12676
|
+
{
|
|
12677
|
+
type: "text",
|
|
12678
|
+
text: JSON.stringify(
|
|
12679
|
+
{
|
|
12680
|
+
stackNumber: result.stack.stackNumber,
|
|
12681
|
+
trunk: result.stack.trunk,
|
|
12682
|
+
threads: result.threads.map((t) => ({
|
|
12683
|
+
id: t.id,
|
|
12684
|
+
title: t.title,
|
|
12685
|
+
branchName: t.branchName,
|
|
12686
|
+
stackLayer: t.stackLayer,
|
|
12687
|
+
worktreePath: t.worktreePath,
|
|
12688
|
+
link: `sideboard://thread/${t.id}`
|
|
12689
|
+
}))
|
|
12690
|
+
},
|
|
12691
|
+
null,
|
|
12692
|
+
2
|
|
12693
|
+
)
|
|
12694
|
+
}
|
|
12695
|
+
]
|
|
12696
|
+
};
|
|
12697
|
+
}
|
|
12698
|
+
);
|
|
11686
12699
|
server.tool(
|
|
11687
12700
|
"list_issues",
|
|
11688
12701
|
"List issues from Sideboard Account connections (Linear API or GitHub Issues; Linear\u2192GitHub fallback when Linear is not connected). Pass repoPath from list_workspaces \u2014 GitHub Issues are scoped to that repo. Then create_thread with sourceType=ticket.",
|