@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.
Files changed (40) hide show
  1. package/dist/agents/cursor-runner.cjs +43 -4
  2. package/dist/agents/cursor-runner.js +45 -6
  3. package/dist/{agents-HIEJL3UV.js → agents-KP7UJEHJ.js} +1 -1
  4. package/dist/{agents-5ROTZNCX.js → agents-KYACODJ3.js} +2 -2
  5. package/dist/{chunk-5ZPSH7VI.js → chunk-A6HVEMIB.js} +16 -7
  6. package/dist/chunk-B3SJXYIJ.js +24 -0
  7. package/dist/{chunk-7EUWSBWR.js → chunk-BZST4HMJ.js} +21 -6
  8. package/dist/{chunk-ZH5QZ4CR.js → chunk-DZFH2KLT.js} +280 -7
  9. package/dist/chunk-FKOIHGKV.js +21 -0
  10. package/dist/{chunk-E4VVEKAM.js → chunk-GNML24AW.js} +2 -2
  11. package/dist/{chunk-EOYDCKQC.js → chunk-HLEX5AQ6.js} +4 -0
  12. package/dist/{chunk-K3WMKGFY.js → chunk-LRLKJM3O.js} +2 -1
  13. package/dist/chunk-N5PM7HGQ.js +103 -0
  14. package/dist/chunk-QTUESPAW.js +101 -0
  15. package/dist/{chunk-O6W3P7V3.js → chunk-TSRXOSVD.js} +4 -0
  16. package/dist/{chunk-F4Q3IM6V.js → chunk-UEAHMGHW.js} +2 -1
  17. package/dist/{chunk-J5JTEJ5O.js → chunk-VG22SETP.js} +6 -0
  18. package/dist/{chunk-XRSAGVRW.js → chunk-XOU6HNQJ.js} +245 -7
  19. package/dist/{chunk-O5DOO7DP.js → chunk-XX5BB7NV.js} +3 -3
  20. package/dist/{chunk-QN7XNQAT.js → chunk-YDXQ72MD.js} +2 -2
  21. package/dist/{chunk-YFJ4FG2P.js → chunk-YOWIYAVA.js} +3 -3
  22. package/dist/{coordinator-prompt-7HHJRO7B.js → coordinator-prompt-6FXVTSFN.js} +4 -3
  23. package/dist/{coordinator-prompt-WD7FAMA2.js → coordinator-prompt-S6JZD5EF.js} +4 -3
  24. package/dist/{global-workspace-OJEPGDXA.js → global-workspace-EV4G2WMQ.js} +5 -4
  25. package/dist/{global-workspace-ECYN2MKL.js → global-workspace-MSX2K27Y.js} +5 -4
  26. package/dist/index.cjs +1383 -149
  27. package/dist/index.d.cts +389 -15
  28. package/dist/index.d.ts +389 -15
  29. package/dist/index.js +883 -91
  30. package/dist/mcp/run-stdio.cjs +1154 -141
  31. package/dist/mcp/run-stdio.js +709 -79
  32. package/dist/plan-file-6O7G4VPQ.js +23 -0
  33. package/dist/plan-file-PHVKUAEE.js +25 -0
  34. package/dist/{thread-store-XICUWFNM.js → thread-store-GHOADGL2.js} +1 -1
  35. package/dist/{thread-store-OV2X6PYO.js → thread-store-UJIGMI5J.js} +1 -1
  36. package/dist/{workspaces-MUU7RGVV.js → workspaces-3RQQZQRO.js} +6 -5
  37. package/dist/{workspaces-ZWOOFZUV.js → workspaces-AYTBR6KQ.js} +6 -5
  38. package/dist/{worktree-DVNDMWZ7.js → worktree-5KEQWSAF.js} +5 -2
  39. package/dist/{worktree-GDV56MX4.js → worktree-RWGL7FUV.js} +5 -2
  40. package/package.json +1 -1
@@ -1,6 +1,9 @@
1
+ import {
2
+ isWorkspaceScratchPath
3
+ } from "./chunk-FKOIHGKV.js";
1
4
  import {
2
5
  listThreads
3
- } from "./chunk-O6W3P7V3.js";
6
+ } from "./chunk-TSRXOSVD.js";
4
7
  import {
5
8
  worktreesRoot
6
9
  } from "./chunk-M37RITA6.js";
@@ -970,6 +973,206 @@ function buildMergeGateChecks(gate, opts = {}) {
970
973
  return rows;
971
974
  }
972
975
 
976
+ // src/git/stack.ts
977
+ var cachedStatus = null;
978
+ var STATUS_TTL_MS = 6e4;
979
+ async function detectGhStack(cwd) {
980
+ const now = Date.now();
981
+ if (cachedStatus && now - cachedStatus.at < STATUS_TTL_MS) {
982
+ return cachedStatus.status;
983
+ }
984
+ const probe = await gh(["stack", "view", "--help"], cwd, { reject: false });
985
+ if (probe.exitCode === 0) {
986
+ const status2 = { available: true };
987
+ cachedStatus = { at: now, status: status2 };
988
+ return status2;
989
+ }
990
+ const err = `${probe.stderr}
991
+ ${probe.stdout}`;
992
+ 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";
993
+ const status = { available: false, reason };
994
+ cachedStatus = { at: now, status };
995
+ return status;
996
+ }
997
+ function resetGhStackDetectCache() {
998
+ cachedStatus = null;
999
+ }
1000
+ function str(v) {
1001
+ return typeof v === "string" ? v : v == null ? "" : String(v);
1002
+ }
1003
+ function num(v) {
1004
+ if (typeof v === "number" && Number.isFinite(v)) return v;
1005
+ if (typeof v === "string" && v.trim() && Number.isFinite(Number(v))) {
1006
+ return Number(v);
1007
+ }
1008
+ return null;
1009
+ }
1010
+ function parseGhStackViewJson(raw) {
1011
+ let data;
1012
+ try {
1013
+ data = JSON.parse(raw);
1014
+ } catch {
1015
+ return null;
1016
+ }
1017
+ if (!Array.isArray(data.branches) || data.branches.length === 0) return null;
1018
+ const trunk = str(data.trunk) || "main";
1019
+ const currentBranch2 = str(data.currentBranch);
1020
+ const stackNumber = num(data.stackNumber) ?? num(data.number);
1021
+ const layers = [];
1022
+ for (let i = 0; i < data.branches.length; i++) {
1023
+ const b = data.branches[i];
1024
+ if (!b || typeof b !== "object") continue;
1025
+ const name = str(b.name);
1026
+ if (!name) continue;
1027
+ const pr = b.pr && typeof b.pr === "object" ? b.pr : null;
1028
+ layers.push({
1029
+ position: i + 1,
1030
+ branchName: name,
1031
+ headSha: str(b.head) || void 0,
1032
+ baseSha: str(b.base) || void 0,
1033
+ isCurrent: Boolean(b.isCurrent) || name === currentBranch2,
1034
+ isMerged: Boolean(b.isMerged),
1035
+ isQueued: Boolean(b.isQueued),
1036
+ needsRebase: Boolean(b.needsRebase),
1037
+ prNumber: pr ? num(pr.number) : null,
1038
+ prUrl: pr && str(pr.url) ? str(pr.url) : null,
1039
+ prState: pr && str(pr.state) ? str(pr.state).toUpperCase() : null,
1040
+ title: pr && str(pr.title) ? str(pr.title) : void 0
1041
+ });
1042
+ }
1043
+ if (!layers.length) return null;
1044
+ let currentIndex = layers.findIndex((l) => l.isCurrent);
1045
+ if (currentIndex < 0 && currentBranch2) {
1046
+ currentIndex = layers.findIndex((l) => l.branchName === currentBranch2);
1047
+ }
1048
+ const { readyToMerge, blockedReason } = stackMergeReadiness(layers, currentIndex);
1049
+ return {
1050
+ stackNumber,
1051
+ trunk,
1052
+ currentBranch: currentBranch2 || layers[currentIndex]?.branchName || layers[0].branchName,
1053
+ layers,
1054
+ currentIndex,
1055
+ readyToMerge,
1056
+ blockedReason
1057
+ };
1058
+ }
1059
+ function stackMergeReadiness(layers, throughIndex) {
1060
+ if (throughIndex < 0 || throughIndex >= layers.length) {
1061
+ return { readyToMerge: false, blockedReason: "Not on a stack layer" };
1062
+ }
1063
+ for (let i = 0; i <= throughIndex; i++) {
1064
+ const layer = layers[i];
1065
+ if (layer.isMerged) continue;
1066
+ if (!layer.prNumber) {
1067
+ return {
1068
+ readyToMerge: false,
1069
+ blockedReason: `Layer ${layer.branchName} has no pull request yet`
1070
+ };
1071
+ }
1072
+ if (layer.needsRebase) {
1073
+ return {
1074
+ readyToMerge: false,
1075
+ blockedReason: `PR #${layer.prNumber} needs rebase`
1076
+ };
1077
+ }
1078
+ const state = (layer.prState ?? "").toUpperCase();
1079
+ if (state && state !== "OPEN" && state !== "QUEUED") {
1080
+ return {
1081
+ readyToMerge: false,
1082
+ blockedReason: `PR #${layer.prNumber} is ${state}`
1083
+ };
1084
+ }
1085
+ }
1086
+ return { readyToMerge: true, blockedReason: null };
1087
+ }
1088
+ async function getPrStack(cwd) {
1089
+ const status = await detectGhStack(cwd);
1090
+ if (!status.available) return null;
1091
+ const result = await gh(["stack", "view", "--json"], cwd, { reject: false });
1092
+ if (result.exitCode === 2) return null;
1093
+ if (result.exitCode !== 0) {
1094
+ if (/not in a stack|no stack/i.test(`${result.stderr}
1095
+ ${result.stdout}`)) {
1096
+ return null;
1097
+ }
1098
+ if (result.exitCode === 9) return null;
1099
+ return null;
1100
+ }
1101
+ const json = result.stdout.trim();
1102
+ if (!json) return null;
1103
+ return parseGhStackViewJson(json);
1104
+ }
1105
+ async function isInPrStack(cwd) {
1106
+ return Boolean(await getPrStack(cwd));
1107
+ }
1108
+ async function mergePrStack(cwd, opts) {
1109
+ const status = await detectGhStack(cwd);
1110
+ if (!status.available) {
1111
+ throw new Error(status.reason);
1112
+ }
1113
+ const method = opts.method ?? "squash";
1114
+ const methodFlag = method === "rebase" ? "--rebase" : method === "merge" ? "--merge" : "--squash";
1115
+ const args = [
1116
+ "stack",
1117
+ "merge",
1118
+ String(opts.through),
1119
+ "--yes",
1120
+ methodFlag
1121
+ ];
1122
+ const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
1123
+ if (exitCode !== 0) {
1124
+ throw new Error(stderr.trim() || stdout.trim() || "gh stack merge failed");
1125
+ }
1126
+ return { stdout };
1127
+ }
1128
+ async function initPrStack(cwd, branches, opts) {
1129
+ if (!branches.length) throw new Error("initPrStack requires at least one branch name");
1130
+ const status = await detectGhStack(cwd);
1131
+ if (!status.available) throw new Error(status.reason);
1132
+ const args = ["stack", "init"];
1133
+ if (opts?.base) args.push("--base", opts.base);
1134
+ args.push(...branches);
1135
+ const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
1136
+ if (exitCode !== 0) {
1137
+ throw new Error(stderr.trim() || stdout.trim() || "gh stack init failed");
1138
+ }
1139
+ }
1140
+ async function addPrStackLayer(cwd, branchName) {
1141
+ if (!branchName.trim()) throw new Error("branch name required");
1142
+ const status = await detectGhStack(cwd);
1143
+ if (!status.available) throw new Error(status.reason);
1144
+ const { exitCode, stderr, stdout } = await gh(
1145
+ ["stack", "add", branchName.trim()],
1146
+ cwd,
1147
+ { reject: false }
1148
+ );
1149
+ if (exitCode !== 0) {
1150
+ throw new Error(stderr.trim() || stdout.trim() || "gh stack add failed");
1151
+ }
1152
+ }
1153
+ async function submitPrStack(cwd, opts) {
1154
+ const status = await detectGhStack(cwd);
1155
+ if (!status.available) throw new Error(status.reason);
1156
+ const args = ["stack", "submit", "--auto"];
1157
+ if (opts?.open) args.push("--open");
1158
+ const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
1159
+ if (exitCode !== 0) {
1160
+ throw new Error(stderr.trim() || stdout.trim() || "gh stack submit failed");
1161
+ }
1162
+ }
1163
+ async function checkoutPrStackLayer(cwd, target) {
1164
+ const status = await detectGhStack(cwd);
1165
+ if (!status.available) throw new Error(status.reason);
1166
+ const { exitCode, stderr, stdout } = await gh(
1167
+ ["stack", "checkout", String(target)],
1168
+ cwd,
1169
+ { reject: false }
1170
+ );
1171
+ if (exitCode !== 0) {
1172
+ throw new Error(stderr.trim() || stdout.trim() || "gh stack checkout failed");
1173
+ }
1174
+ }
1175
+
973
1176
  // src/git/worktree.ts
974
1177
  async function lookupGithubGraphqlReset(cwd) {
975
1178
  const result = await gh(["api", "rate_limit"], cwd, { reject: false });
@@ -1646,6 +1849,52 @@ ${add.stdout}`;
1646
1849
  await ensureGhPreferOrigin(worktreePath);
1647
1850
  return { branchName, worktreePath };
1648
1851
  }
1852
+ async function createExistingBranchWorktree(opts) {
1853
+ const branchName = opts.branchName.trim();
1854
+ if (!branchName) throw new Error("branch name required");
1855
+ const worktreePath = join(worktreesRoot(opts.repoPath), opts.slug);
1856
+ if (existsSync(worktreePath)) {
1857
+ throw new Error(`Worktree already exists at ${worktreePath}`);
1858
+ }
1859
+ await ensureGhPreferOrigin(opts.repoPath);
1860
+ await git(["fetch", "origin", "--prune"], opts.repoPath, { reject: false });
1861
+ if (!branchName.startsWith("origin/") && !branchName.startsWith("refs/")) {
1862
+ await git(["fetch", "origin", branchName], opts.repoPath, { reject: false });
1863
+ }
1864
+ const existing = await listWorktrees(opts.repoPath);
1865
+ const already = existing.find((w) => w.branch === branchName);
1866
+ if (already?.path) {
1867
+ throw new Error(
1868
+ `Branch ${branchName} is already checked out at ${already.path}`
1869
+ );
1870
+ }
1871
+ const startPoint = await resolveWorktreeStartPoint(opts.repoPath, branchName);
1872
+ const add = await git(
1873
+ ["worktree", "add", worktreePath, startPoint],
1874
+ opts.repoPath,
1875
+ { reject: false }
1876
+ );
1877
+ if (add.exitCode !== 0) {
1878
+ const retry = await git(
1879
+ ["worktree", "add", worktreePath, branchName],
1880
+ opts.repoPath,
1881
+ { reject: false }
1882
+ );
1883
+ if (retry.exitCode !== 0) {
1884
+ throw new Error(
1885
+ `Failed to create worktree for ${branchName}: ${retry.stderr.trim() || add.stderr.trim() || retry.stdout.trim() || add.stdout.trim() || `exit ${add.exitCode}`}`
1886
+ );
1887
+ }
1888
+ }
1889
+ const head = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath, {
1890
+ reject: false
1891
+ });
1892
+ if (head.stdout.trim() === "HEAD" || head.stdout.trim() !== branchName) {
1893
+ await git(["checkout", "-B", branchName], worktreePath, { reject: false });
1894
+ }
1895
+ await ensureGhPreferOrigin(worktreePath);
1896
+ return { branchName, worktreePath };
1897
+ }
1649
1898
  async function removeWorktree(repoPath, worktreePath, opts) {
1650
1899
  await git(["worktree", "remove", "--force", worktreePath], repoPath, {
1651
1900
  reject: false
@@ -1685,8 +1934,7 @@ async function isDirty(worktreePath) {
1685
1934
  return false;
1686
1935
  }
1687
1936
  function isSideboardScratchPath(relativePath) {
1688
- const p = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
1689
- return p === ".sideboard/attachments" || p.startsWith(".sideboard/attachments/");
1937
+ return isWorkspaceScratchPath(relativePath);
1690
1938
  }
1691
1939
  function porcelainStatusPath(line) {
1692
1940
  const rest = line.length >= 3 ? line.slice(3) : "";
@@ -1714,7 +1962,7 @@ async function pushBranch(worktreePath, branchName) {
1714
1962
  }
1715
1963
  async function mergePr(cwd, selector, opts) {
1716
1964
  const slug = await resolveGithubRepoSlug(cwd);
1717
- const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft"];
1965
+ const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft,number"];
1718
1966
  if (slug) viewArgs.push("--repo", slug);
1719
1967
  const before = await gh(viewArgs, cwd, { reject: false });
1720
1968
  if (before.exitCode !== 0 || !before.stdout.trim()) {
@@ -1722,16 +1970,29 @@ async function mergePr(cwd, selector, opts) {
1722
1970
  }
1723
1971
  let url = "";
1724
1972
  let isDraft = false;
1973
+ let prNumber = null;
1725
1974
  try {
1726
1975
  const parsed = JSON.parse(before.stdout);
1727
1976
  url = String(parsed.url ?? "");
1728
1977
  isDraft = Boolean(parsed.isDraft);
1978
+ prNumber = typeof parsed.number === "number" && Number.isFinite(parsed.number) ? parsed.number : null;
1729
1979
  if (String(parsed.state ?? "").toUpperCase() === "MERGED") {
1730
1980
  return { url, state: "MERGED" };
1731
1981
  }
1732
1982
  } catch {
1733
1983
  throw new Error("Could not parse pull request details");
1734
1984
  }
1985
+ const stack = await getPrStack(cwd);
1986
+ const stackLayer = stack && prNumber != null ? stack.layers.find((l) => l.prNumber === prNumber) : null;
1987
+ if (stack && stackLayer && prNumber != null) {
1988
+ const throughIndex = stack.layers.findIndex((l) => l.prNumber === prNumber);
1989
+ const gate = stackMergeReadiness(stack.layers, throughIndex);
1990
+ if (!gate.readyToMerge) {
1991
+ throw new Error(gate.blockedReason || "Stack is not ready to merge");
1992
+ }
1993
+ await mergePrStack(cwd, { through: prNumber, method: opts?.method ?? "squash" });
1994
+ return { url, state: "MERGED" };
1995
+ }
1735
1996
  if (isDraft) {
1736
1997
  const readyArgs = ["pr", "ready", selector];
1737
1998
  if (slug) readyArgs.push("--repo", slug);
@@ -1753,10 +2014,10 @@ async function mergePr(cwd, selector, opts) {
1753
2014
  const after = await gh(viewArgs, cwd, { reject: false });
1754
2015
  if (after.exitCode === 0 && after.stdout.trim()) {
1755
2016
  try {
1756
- const parsed = JSON.parse(after.stdout);
2017
+ const parsed = after.stdout ? JSON.parse(after.stdout) : null;
1757
2018
  return {
1758
- url: String(parsed.url ?? url),
1759
- state: String(parsed.state ?? "MERGED")
2019
+ url: String(parsed?.url ?? url),
2020
+ state: String(parsed?.state ?? "MERGED")
1760
2021
  };
1761
2022
  } catch {
1762
2023
  }
@@ -1910,6 +2171,17 @@ export {
1910
2171
  extractGhErrorDetail,
1911
2172
  formatGhLandError,
1912
2173
  formatIpcInvokeError,
2174
+ detectGhStack,
2175
+ resetGhStackDetectCache,
2176
+ parseGhStackViewJson,
2177
+ stackMergeReadiness,
2178
+ getPrStack,
2179
+ isInPrStack,
2180
+ mergePrStack,
2181
+ initPrStack,
2182
+ addPrStackLayer,
2183
+ submitPrStack,
2184
+ checkoutPrStackLayer,
1913
2185
  slugify,
1914
2186
  resolveRepoRoot,
1915
2187
  parseGithubSlugFromRemoteUrl,
@@ -1931,6 +2203,7 @@ export {
1931
2203
  fetchPrHead,
1932
2204
  resolveWorktreeStartPoint,
1933
2205
  createThreadWorktree,
2206
+ createExistingBranchWorktree,
1934
2207
  removeWorktree,
1935
2208
  listWorktrees,
1936
2209
  isDirty,
@@ -0,0 +1,21 @@
1
+ // src/paths/workspace-scratch.ts
2
+ var ATTACHMENTS_DIR = ".context/attachments";
3
+ var LEGACY_ATTACHMENTS_DIR = ".sideboard/attachments";
4
+ var ATTACHMENTS_GITIGNORE = `# Sideboard / workspace attachments (local only)
5
+ *
6
+ !.gitignore
7
+ `;
8
+ function attachmentsGitignoreBody() {
9
+ return ATTACHMENTS_GITIGNORE;
10
+ }
11
+ function isWorkspaceScratchPath(relativePath) {
12
+ const p = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
13
+ return p === ATTACHMENTS_DIR || p.startsWith(`${ATTACHMENTS_DIR}/`) || p === LEGACY_ATTACHMENTS_DIR || p.startsWith(`${LEGACY_ATTACHMENTS_DIR}/`) || p === ".context" || p.startsWith(".context/");
14
+ }
15
+
16
+ export {
17
+ ATTACHMENTS_DIR,
18
+ LEGACY_ATTACHMENTS_DIR,
19
+ attachmentsGitignoreBody,
20
+ isWorkspaceScratchPath
21
+ };
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  isGlobalRepoPath
3
- } from "./chunk-O5DOO7DP.js";
3
+ } from "./chunk-XX5BB7NV.js";
4
4
  import {
5
5
  ensureGhPreferOrigin,
6
6
  resolveRepoRoot
7
- } from "./chunk-ZH5QZ4CR.js";
7
+ } from "./chunk-DZFH2KLT.js";
8
8
  import {
9
9
  appDataDir
10
10
  } from "./chunk-M37RITA6.js";
@@ -41,6 +41,8 @@ function normalizeThread(raw) {
41
41
  agentPid: raw.agentPid ?? null,
42
42
  attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
43
43
  prTitle: raw.prTitle ?? null,
44
+ stackId: raw.stackId ?? null,
45
+ stackLayer: raw.stackLayer ?? null,
44
46
  userSetTitle: Boolean(raw.userSetTitle),
45
47
  activeRuns: Array.isArray(raw.activeRuns) ? raw.activeRuns : [],
46
48
  quotaResumeAt: raw.quotaResumeAt ?? null,
@@ -65,6 +67,8 @@ function createEmptyThread(partial) {
65
67
  activeRuns: partial.activeRuns ?? [],
66
68
  prUrl: partial.prUrl ?? null,
67
69
  prTitle: partial.prTitle ?? null,
70
+ stackId: partial.stackId ?? null,
71
+ stackLayer: partial.stackLayer ?? null,
68
72
  userSetTitle: partial.userSetTitle ?? false,
69
73
  messages: partial.messages ?? [],
70
74
  attachments: partial.attachments ?? [],
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  resolveGithubRepoSlug
3
- } from "./chunk-ZH5QZ4CR.js";
3
+ } from "./chunk-DZFH2KLT.js";
4
4
  import {
5
5
  globalAgentCwd,
6
6
  sideboardReposDir
@@ -42,6 +42,7 @@ var COORDINATOR_TOOL_PLAYBOOK = [
42
42
  "Discover:",
43
43
  "- list_workspaces \u2014 registered repos (path + github slug when known)",
44
44
  "- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces (issues: Linear API or GitHub Issues)",
45
+ "- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
45
46
  "- list_models \u2014 only when you need a specific model (rare); otherwise leave model unset = Auto",
46
47
  "- list_threads / get_thread \u2014 fleet status (what is going on)",
47
48
  "Workspaces:",
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ ATTACHMENTS_DIR,
5
+ LEGACY_ATTACHMENTS_DIR,
6
+ attachmentsGitignoreBody
7
+ } from "./chunk-B3SJXYIJ.js";
8
+
9
+ // src/plan/plan-file.ts
10
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
11
+ import { dirname, join } from "path";
12
+
13
+ // src/plan/plan-present.ts
14
+ var PLAN_FILE_REL = `${ATTACHMENTS_DIR}/plan.md`;
15
+ var PLAN_FILE_NAME = "plan.md";
16
+ var LEGACY_PLAN_FILE_REL = ".sideboard/plan.md";
17
+ function asRecord(v) {
18
+ return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
19
+ }
20
+ function isPresentPlanToolName(name) {
21
+ if (!name) return false;
22
+ return /present_plan$/i.test(name) || /^mcp__sideboard__present_plan$/i.test(name);
23
+ }
24
+ function extractPresentedPlan(parts) {
25
+ if (!parts?.length) return null;
26
+ for (let i = parts.length - 1; i >= 0; i--) {
27
+ const p = parts[i];
28
+ if (p.type !== "tool" || !isPresentPlanToolName(p.name)) continue;
29
+ const input = asRecord(p.input) ?? {};
30
+ const content = typeof input.content === "string" ? input.content : typeof input.plan === "string" ? input.plan : typeof input.markdown === "string" ? input.markdown : "";
31
+ if (!content.trim()) continue;
32
+ const title = typeof input.title === "string" && input.title.trim() ? input.title.trim() : "Plan";
33
+ const path = typeof input.path === "string" && input.path.trim() ? input.path.trim() : PLAN_FILE_REL;
34
+ return { title, content: content.trim(), path, source: "present_plan" };
35
+ }
36
+ return null;
37
+ }
38
+ function resolvePlanMarkdown(opts) {
39
+ const fromTool = extractPresentedPlan(opts.parts);
40
+ if (fromTool) return fromTool;
41
+ const file = opts.fileContent?.trim();
42
+ if (file) {
43
+ return {
44
+ title: "Plan",
45
+ content: file,
46
+ path: PLAN_FILE_REL,
47
+ source: "exit_plan"
48
+ };
49
+ }
50
+ const text = opts.text?.trim();
51
+ if (text && text.length >= 80) {
52
+ return {
53
+ title: "Plan",
54
+ content: text,
55
+ path: PLAN_FILE_REL,
56
+ source: "text"
57
+ };
58
+ }
59
+ return null;
60
+ }
61
+
62
+ // src/plan/plan-file.ts
63
+ function ensureAttachmentsGitignore(worktreePath) {
64
+ const gitignoreAbs = join(worktreePath, ATTACHMENTS_DIR, ".gitignore");
65
+ if (existsSync(gitignoreAbs)) return;
66
+ mkdirSync(dirname(gitignoreAbs), { recursive: true });
67
+ writeFileSync(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
68
+ }
69
+ function planFileAbs(worktreePath) {
70
+ return join(worktreePath, PLAN_FILE_REL);
71
+ }
72
+ function readTextIfPresent(abs) {
73
+ if (!existsSync(abs)) return null;
74
+ try {
75
+ const content = readFileSync(abs, "utf8");
76
+ return content.trim() ? content : null;
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+ function readPlanFile(worktreePath) {
82
+ return readTextIfPresent(planFileAbs(worktreePath)) ?? readTextIfPresent(join(worktreePath, `${LEGACY_ATTACHMENTS_DIR}/plan.md`)) ?? readTextIfPresent(join(worktreePath, LEGACY_PLAN_FILE_REL));
83
+ }
84
+ function writePlanFile(worktreePath, content) {
85
+ ensureAttachmentsGitignore(worktreePath);
86
+ const abs = planFileAbs(worktreePath);
87
+ mkdirSync(dirname(abs), { recursive: true });
88
+ const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
89
+ writeFileSync(abs, body, "utf8");
90
+ return PLAN_FILE_REL;
91
+ }
92
+
93
+ export {
94
+ PLAN_FILE_REL,
95
+ PLAN_FILE_NAME,
96
+ LEGACY_PLAN_FILE_REL,
97
+ isPresentPlanToolName,
98
+ extractPresentedPlan,
99
+ resolvePlanMarkdown,
100
+ planFileAbs,
101
+ readPlanFile,
102
+ writePlanFile
103
+ };
@@ -0,0 +1,101 @@
1
+ import {
2
+ ATTACHMENTS_DIR,
3
+ LEGACY_ATTACHMENTS_DIR,
4
+ attachmentsGitignoreBody
5
+ } from "./chunk-FKOIHGKV.js";
6
+
7
+ // src/plan/plan-file.ts
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
9
+ import { dirname, join } from "path";
10
+
11
+ // src/plan/plan-present.ts
12
+ var PLAN_FILE_REL = `${ATTACHMENTS_DIR}/plan.md`;
13
+ var PLAN_FILE_NAME = "plan.md";
14
+ var LEGACY_PLAN_FILE_REL = ".sideboard/plan.md";
15
+ function asRecord(v) {
16
+ return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
17
+ }
18
+ function isPresentPlanToolName(name) {
19
+ if (!name) return false;
20
+ return /present_plan$/i.test(name) || /^mcp__sideboard__present_plan$/i.test(name);
21
+ }
22
+ function extractPresentedPlan(parts) {
23
+ if (!parts?.length) return null;
24
+ for (let i = parts.length - 1; i >= 0; i--) {
25
+ const p = parts[i];
26
+ if (p.type !== "tool" || !isPresentPlanToolName(p.name)) continue;
27
+ const input = asRecord(p.input) ?? {};
28
+ const content = typeof input.content === "string" ? input.content : typeof input.plan === "string" ? input.plan : typeof input.markdown === "string" ? input.markdown : "";
29
+ if (!content.trim()) continue;
30
+ const title = typeof input.title === "string" && input.title.trim() ? input.title.trim() : "Plan";
31
+ const path = typeof input.path === "string" && input.path.trim() ? input.path.trim() : PLAN_FILE_REL;
32
+ return { title, content: content.trim(), path, source: "present_plan" };
33
+ }
34
+ return null;
35
+ }
36
+ function resolvePlanMarkdown(opts) {
37
+ const fromTool = extractPresentedPlan(opts.parts);
38
+ if (fromTool) return fromTool;
39
+ const file = opts.fileContent?.trim();
40
+ if (file) {
41
+ return {
42
+ title: "Plan",
43
+ content: file,
44
+ path: PLAN_FILE_REL,
45
+ source: "exit_plan"
46
+ };
47
+ }
48
+ const text = opts.text?.trim();
49
+ if (text && text.length >= 80) {
50
+ return {
51
+ title: "Plan",
52
+ content: text,
53
+ path: PLAN_FILE_REL,
54
+ source: "text"
55
+ };
56
+ }
57
+ return null;
58
+ }
59
+
60
+ // src/plan/plan-file.ts
61
+ function ensureAttachmentsGitignore(worktreePath) {
62
+ const gitignoreAbs = join(worktreePath, ATTACHMENTS_DIR, ".gitignore");
63
+ if (existsSync(gitignoreAbs)) return;
64
+ mkdirSync(dirname(gitignoreAbs), { recursive: true });
65
+ writeFileSync(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
66
+ }
67
+ function planFileAbs(worktreePath) {
68
+ return join(worktreePath, PLAN_FILE_REL);
69
+ }
70
+ function readTextIfPresent(abs) {
71
+ if (!existsSync(abs)) return null;
72
+ try {
73
+ const content = readFileSync(abs, "utf8");
74
+ return content.trim() ? content : null;
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+ function readPlanFile(worktreePath) {
80
+ return readTextIfPresent(planFileAbs(worktreePath)) ?? readTextIfPresent(join(worktreePath, `${LEGACY_ATTACHMENTS_DIR}/plan.md`)) ?? readTextIfPresent(join(worktreePath, LEGACY_PLAN_FILE_REL));
81
+ }
82
+ function writePlanFile(worktreePath, content) {
83
+ ensureAttachmentsGitignore(worktreePath);
84
+ const abs = planFileAbs(worktreePath);
85
+ mkdirSync(dirname(abs), { recursive: true });
86
+ const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
87
+ writeFileSync(abs, body, "utf8");
88
+ return PLAN_FILE_REL;
89
+ }
90
+
91
+ export {
92
+ PLAN_FILE_REL,
93
+ PLAN_FILE_NAME,
94
+ LEGACY_PLAN_FILE_REL,
95
+ isPresentPlanToolName,
96
+ extractPresentedPlan,
97
+ resolvePlanMarkdown,
98
+ planFileAbs,
99
+ readPlanFile,
100
+ writePlanFile
101
+ };
@@ -39,6 +39,8 @@ function normalizeThread(raw) {
39
39
  agentPid: raw.agentPid ?? null,
40
40
  attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
41
41
  prTitle: raw.prTitle ?? null,
42
+ stackId: raw.stackId ?? null,
43
+ stackLayer: raw.stackLayer ?? null,
42
44
  userSetTitle: Boolean(raw.userSetTitle),
43
45
  activeRuns: Array.isArray(raw.activeRuns) ? raw.activeRuns : [],
44
46
  quotaResumeAt: raw.quotaResumeAt ?? null,
@@ -63,6 +65,8 @@ function createEmptyThread(partial) {
63
65
  activeRuns: partial.activeRuns ?? [],
64
66
  prUrl: partial.prUrl ?? null,
65
67
  prTitle: partial.prTitle ?? null,
68
+ stackId: partial.stackId ?? null,
69
+ stackLayer: partial.stackLayer ?? null,
66
70
  userSetTitle: partial.userSetTitle ?? false,
67
71
  messages: partial.messages ?? [],
68
72
  attachments: partial.attachments ?? [],
@@ -2,7 +2,7 @@
2
2
 
3
3
  import {
4
4
  resolveGithubRepoSlug
5
- } from "./chunk-XRSAGVRW.js";
5
+ } from "./chunk-XOU6HNQJ.js";
6
6
  import {
7
7
  globalAgentCwd,
8
8
  sideboardReposDir
@@ -44,6 +44,7 @@ var COORDINATOR_TOOL_PLAYBOOK = [
44
44
  "Discover:",
45
45
  "- list_workspaces \u2014 registered repos (path + github slug when known)",
46
46
  "- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces (issues: Linear API or GitHub Issues)",
47
+ "- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
47
48
  "- list_models \u2014 only when you need a specific model (rare); otherwise leave model unset = Auto",
48
49
  "- list_threads / get_thread \u2014 fleet status (what is going on)",
49
50
  "Workspaces:",
@@ -60,6 +60,11 @@ function summarizeTurnStderr(tail, maxChars = 500) {
60
60
  if (joined.length <= maxChars) return joined;
61
61
  return joined.slice(joined.length - maxChars);
62
62
  }
63
+ function looksLikeInvalidAgentSession(text) {
64
+ const lower = text.trim().toLowerCase();
65
+ if (!lower) return false;
66
+ 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);
67
+ }
63
68
  function looksLikeAgentFailureMessage(text) {
64
69
  const lower = text.trim().toLowerCase();
65
70
  if (!lower) return false;
@@ -212,6 +217,7 @@ export {
212
217
  extractJsonErrorMessage,
213
218
  pushTurnStderr,
214
219
  summarizeTurnStderr,
220
+ looksLikeInvalidAgentSession,
215
221
  looksLikeAgentFailureMessage,
216
222
  fallbackTurnFailDetail,
217
223
  humanizeAgentFailDetail,