@sideboard-ai/core 0.1.50 → 0.1.52

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 (46) hide show
  1. package/dist/{agents-LUB3Q773.js → agents-ON6RKKND.js} +3 -3
  2. package/dist/{agents-3P4N6KHC.js → agents-YKSS6VBO.js} +3 -3
  3. package/dist/{chunk-RQI4TOXK.js → chunk-6QTZVJ7A.js} +7 -5
  4. package/dist/{chunk-AJ6ROGD7.js → chunk-7LNGIP3X.js} +20 -2
  5. package/dist/chunk-B3SJXYIJ.js +24 -0
  6. package/dist/{chunk-ILQK4P5R.js → chunk-BXJ76RHF.js} +4 -4
  7. package/dist/{chunk-SS34TVSY.js → chunk-D3METLRW.js} +7 -5
  8. package/dist/{chunk-FV6FN6V5.js → chunk-DZFH2KLT.js} +340 -22
  9. package/dist/chunk-FKOIHGKV.js +21 -0
  10. package/dist/{chunk-3NAS4MWH.js → chunk-GNML24AW.js} +2 -2
  11. package/dist/{chunk-E35APBHV.js → chunk-H6GGDLYS.js} +1 -1
  12. package/dist/{chunk-EOYDCKQC.js → chunk-HLEX5AQ6.js} +4 -0
  13. package/dist/{chunk-ZQCQIWIP.js → chunk-LRLKJM3O.js} +2 -1
  14. package/dist/chunk-N5PM7HGQ.js +103 -0
  15. package/dist/chunk-QTUESPAW.js +101 -0
  16. package/dist/{chunk-O6W3P7V3.js → chunk-TSRXOSVD.js} +4 -0
  17. package/dist/{chunk-GZXBYHJC.js → chunk-UEAHMGHW.js} +2 -1
  18. package/dist/{chunk-ZVV5EEQN.js → chunk-XOU6HNQJ.js} +305 -22
  19. package/dist/{chunk-5WYEJ3F3.js → chunk-XX5BB7NV.js} +3 -3
  20. package/dist/{chunk-V4JRXYYU.js → chunk-YDXQ72MD.js} +2 -2
  21. package/dist/{chunk-DF5VQQKA.js → chunk-YOWIYAVA.js} +3 -3
  22. package/dist/{chunk-ISY4EGF6.js → chunk-ZNM4MAN4.js} +20 -2
  23. package/dist/{connected-teams-UBXHZGO4.js → connected-teams-2ANNZCP5.js} +2 -2
  24. package/dist/{connected-teams-GF52Q7LB.js → connected-teams-ONC4666A.js} +2 -2
  25. package/dist/{coordinator-prompt-VG4BZ5JL.js → coordinator-prompt-6FXVTSFN.js} +5 -4
  26. package/dist/{coordinator-prompt-SPGDT7J5.js → coordinator-prompt-S6JZD5EF.js} +5 -4
  27. package/dist/{global-workspace-OJF4ENH4.js → global-workspace-EV4G2WMQ.js} +6 -5
  28. package/dist/{global-workspace-KSR3E63K.js → global-workspace-MSX2K27Y.js} +6 -5
  29. package/dist/index.cjs +1369 -155
  30. package/dist/index.d.cts +397 -14
  31. package/dist/index.d.ts +397 -14
  32. package/dist/index.js +811 -90
  33. package/dist/mcp/run-stdio.cjs +1136 -147
  34. package/dist/mcp/run-stdio.js +635 -79
  35. package/dist/plan-file-6O7G4VPQ.js +23 -0
  36. package/dist/plan-file-PHVKUAEE.js +25 -0
  37. package/dist/{run-HMRSRG3U.js → run-H3IMNOYN.js} +3 -1
  38. package/dist/run-ZJKYHXGN.js +12 -0
  39. package/dist/{thread-store-XICUWFNM.js → thread-store-GHOADGL2.js} +1 -1
  40. package/dist/{thread-store-OV2X6PYO.js → thread-store-UJIGMI5J.js} +1 -1
  41. package/dist/{workspaces-OZE7LRDO.js → workspaces-3RQQZQRO.js} +7 -6
  42. package/dist/{workspaces-UJX7JIGH.js → workspaces-AYTBR6KQ.js} +7 -6
  43. package/dist/{worktree-XG5PCLZY.js → worktree-5KEQWSAF.js} +8 -3
  44. package/dist/{worktree-TEDAJ57S.js → worktree-RWGL7FUV.js} +8 -3
  45. package/package.json +1 -1
  46. package/dist/run-LF6E5IKL.js +0 -10
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  listWorkspaces,
5
5
  removeWorkspace,
6
6
  syncWorkspacesFromThreads
7
- } from "./chunk-3NAS4MWH.js";
7
+ } from "./chunk-GNML24AW.js";
8
8
  import {
9
9
  CLOUD_COORDINATOR_BUSY_REPLY,
10
10
  CLOUD_COORDINATOR_STOPPED_REPLY,
@@ -24,7 +24,7 @@ import {
24
24
  orchestratorSessionPoisonedByBuiltins,
25
25
  parseForceStopMessage,
26
26
  takenTeamSlugsForOrchestration
27
- } from "./chunk-5WYEJ3F3.js";
27
+ } from "./chunk-XX5BB7NV.js";
28
28
  import {
29
29
  BRIGHTSY_MCP_ALLOWED_TOOLS,
30
30
  CLAUDE_MODEL_CATALOG,
@@ -70,7 +70,7 @@ import {
70
70
  resolveQuotaFallbackAgent,
71
71
  sanitizeMcpServerName,
72
72
  writeInjectedMcpConfig
73
- } from "./chunk-SS34TVSY.js";
73
+ } from "./chunk-D3METLRW.js";
74
74
  import {
75
75
  ORCHESTRATOR_AGENT_KINDS,
76
76
  assertOrchestratorCapableAgent,
@@ -88,7 +88,18 @@ import {
88
88
  loadBrightsyConfig,
89
89
  saveBrightsyConfig,
90
90
  switchBrightsyAccount
91
- } from "./chunk-ILQK4P5R.js";
91
+ } from "./chunk-BXJ76RHF.js";
92
+ import {
93
+ LEGACY_PLAN_FILE_REL,
94
+ PLAN_FILE_NAME,
95
+ PLAN_FILE_REL,
96
+ extractPresentedPlan,
97
+ isPresentPlanToolName,
98
+ planFileAbs,
99
+ readPlanFile,
100
+ resolvePlanMarkdown,
101
+ writePlanFile
102
+ } from "./chunk-QTUESPAW.js";
92
103
  import {
93
104
  cursorSdkMessageToEvents,
94
105
  fallbackTurnFailDetail,
@@ -144,17 +155,21 @@ import {
144
155
  enrichWorkspacesWithGithub,
145
156
  ensureGlobalCoordinatorCwd,
146
157
  formatWorkspaceInventory
147
- } from "./chunk-ZQCQIWIP.js";
158
+ } from "./chunk-LRLKJM3O.js";
148
159
  import {
149
160
  FAMOUS_SOCCER_TEAMS,
161
+ addPrStackLayer,
150
162
  allocateTeamName,
151
163
  allocateTeamSlug,
152
164
  branchDisplayLabel,
165
+ checkoutPrStackLayer,
153
166
  collectTakenTeamSlugs,
154
167
  commitAll,
168
+ createExistingBranchWorktree,
155
169
  createOrUpdatePr,
156
170
  createThreadWorktree,
157
171
  currentBranch,
172
+ detectGhStack,
158
173
  detectLocalMergeConflicts,
159
174
  ensureGhPreferOrigin,
160
175
  extractGhErrorDetail,
@@ -166,21 +181,28 @@ import {
166
181
  getPrChecks,
167
182
  getPrDetails,
168
183
  getPrMeta,
184
+ getPrStack,
169
185
  ghHeadRef,
170
186
  ghRepoSelectArgs,
187
+ initPrStack,
171
188
  isDirty,
172
189
  isGhRateLimitError,
190
+ isInPrStack,
173
191
  isPlaceholderBranch,
192
+ isSideboardScratchPath,
174
193
  listBranches,
175
194
  listPrs,
176
195
  listWorktrees,
177
196
  lookupSoccerTeam,
178
197
  mergePr,
198
+ mergePrStack,
179
199
  normalizeWorktreePath,
180
200
  originGhRepoEnv,
201
+ parseGhStackViewJson,
181
202
  parseGithubSlugFromRemoteUrl,
182
203
  pushBranch,
183
204
  removeWorktree,
205
+ resetGhStackDetectCache,
184
206
  resolveDefaultBranch,
185
207
  resolveDiffBaseRef,
186
208
  resolveGithubRepoSlug,
@@ -188,13 +210,21 @@ import {
188
210
  resolveRepoRoot,
189
211
  resolveWorktreeStartPoint,
190
212
  slugify,
213
+ stackMergeReadiness,
214
+ submitPrStack,
191
215
  suggestSlug,
192
216
  takenSlugsFromThread,
193
217
  threadDisplayLabel,
194
218
  worktreeDisplayLabel,
195
219
  worktreeDisplayLabelForGroup,
196
220
  worktreeNameFromPath
197
- } from "./chunk-FV6FN6V5.js";
221
+ } from "./chunk-DZFH2KLT.js";
222
+ import {
223
+ ATTACHMENTS_DIR,
224
+ LEGACY_ATTACHMENTS_DIR,
225
+ attachmentsGitignoreBody,
226
+ isWorkspaceScratchPath
227
+ } from "./chunk-FKOIHGKV.js";
198
228
  import {
199
229
  appendMessage,
200
230
  createEmptyThread,
@@ -208,7 +238,7 @@ import {
208
238
  updateThread,
209
239
  withThreadLock,
210
240
  writeThread
211
- } from "./chunk-O6W3P7V3.js";
241
+ } from "./chunk-TSRXOSVD.js";
212
242
  import {
213
243
  THINKING_EFFORTS,
214
244
  isThinkingEffort,
@@ -243,8 +273,9 @@ import {
243
273
  ensureAgentPath,
244
274
  gh,
245
275
  git,
276
+ resolveGhAuthToken,
246
277
  run
247
- } from "./chunk-AJ6ROGD7.js";
278
+ } from "./chunk-7LNGIP3X.js";
248
279
 
249
280
  // src/integrations/github.ts
250
281
  async function getGitHubStatus() {
@@ -456,6 +487,9 @@ function toolDescription(name, input) {
456
487
  if (/present_artifact$/i.test(name)) {
457
488
  return str(input?.title) ? `Present ${str(input?.title)}` : "Present artifact";
458
489
  }
490
+ if (/present_plan$/i.test(name)) {
491
+ return str(input?.title) ? `Plan ${str(input?.title)}` : "Present plan";
492
+ }
459
493
  if (/present_schema$/i.test(name)) {
460
494
  return str(input?.title) ? `Schema ${str(input?.title)}` : "Present schema";
461
495
  }
@@ -655,9 +689,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
655
689
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
656
690
  );
657
691
  }
658
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-KSR3E63K.js");
692
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-MSX2K27Y.js");
659
693
  if (isGlobalThread2(thread)) {
660
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-SPGDT7J5.js");
694
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-S6JZD5EF.js");
661
695
  ensureGlobalCoordinatorCwd2();
662
696
  }
663
697
  if (isOrchestratorThread(thread)) {
@@ -1496,7 +1530,7 @@ async function listUntrackedPaths(worktreePath) {
1496
1530
  worktreePath,
1497
1531
  { reject: false }
1498
1532
  );
1499
- return stdout.split("\0").filter(Boolean);
1533
+ return stdout.split("\0").filter(Boolean).filter((p) => !isSideboardScratchPath(p));
1500
1534
  }
1501
1535
  async function listUntrackedFiles(worktreePath, maxHunk) {
1502
1536
  const paths = await listUntrackedPaths(worktreePath);
@@ -2179,11 +2213,6 @@ var IMAGE_MIME_BY_EXT = {
2179
2213
  bmp: "image/bmp",
2180
2214
  ico: "image/x-icon"
2181
2215
  };
2182
- var ATTACHMENTS_DIR = ".sideboard/attachments";
2183
- var ATTACHMENTS_GITIGNORE = `# Sideboard review / composer attachments (local only)
2184
- *
2185
- !.gitignore
2186
- `;
2187
2216
  var MAX_INLINE_BYTES = 4e5;
2188
2217
  var MAX_PREVIEW_BYTES = 5e6;
2189
2218
  function fileExtension(filePath) {
@@ -2201,7 +2230,7 @@ function ensureAttachmentsDir(worktreePath) {
2201
2230
  mkdirSync3(dir, { recursive: true });
2202
2231
  const gi = join6(dir, ".gitignore");
2203
2232
  if (!existsSync6(gi)) {
2204
- writeFileSync2(gi, ATTACHMENTS_GITIGNORE, "utf8");
2233
+ writeFileSync2(gi, attachmentsGitignoreBody(), "utf8");
2205
2234
  }
2206
2235
  return dir;
2207
2236
  }
@@ -2781,7 +2810,7 @@ async function previewLand(thread) {
2781
2810
  const target = await resolveDefaultBranch(thread.repoPath);
2782
2811
  if (thread.branchName === target || thread.branchName === "main" || thread.branchName === "master") {
2783
2812
  }
2784
- const { stdout: head } = await import("./run-LF6E5IKL.js").then(
2813
+ const { stdout: head } = await import("./run-ZJKYHXGN.js").then(
2785
2814
  ({ git: git2 }) => git2(["rev-parse", "--abbrev-ref", "HEAD"], thread.worktreePath)
2786
2815
  );
2787
2816
  const current = head.trim();
@@ -2820,7 +2849,7 @@ async function confirmLand(thread, opts) {
2820
2849
  if (preview.dirty) {
2821
2850
  committed = await commitAll(thread.worktreePath, meta.commitMessage);
2822
2851
  }
2823
- const { git: git2 } = await import("./run-LF6E5IKL.js");
2852
+ const { git: git2 } = await import("./run-ZJKYHXGN.js");
2824
2853
  const { stdout: headOut } = await git2(
2825
2854
  ["rev-parse", "--abbrev-ref", "HEAD"],
2826
2855
  thread.worktreePath,
@@ -2851,7 +2880,7 @@ async function confirmLand(thread, opts) {
2851
2880
 
2852
2881
  // src/threads/create.ts
2853
2882
  import { existsSync as existsSync7 } from "fs";
2854
- async function createThread(input, onSetupLine) {
2883
+ async function createThread(input, _onSetupLine) {
2855
2884
  await requireAgent(input.agent);
2856
2885
  const repoPath = await resolveRepoRoot(input.repoPath);
2857
2886
  if (!existsSync7(repoPath)) {
@@ -2909,26 +2938,10 @@ async function createThread(input, onSetupLine) {
2909
2938
  });
2910
2939
  writeThread(thread);
2911
2940
  await ensureWorkspace(repoPath);
2912
- try {
2913
- let setup = await runSetupScript(repoPath, worktreePath, onSetupLine);
2914
- if (!setup.ran) {
2915
- setup = await runCursorWorktreeSetup(repoPath, worktreePath, onSetupLine);
2916
- }
2917
- if (setup.ran && setup.exitCode !== 0 && setup.exitCode !== null) {
2918
- updateThread(thread.id, {
2919
- lastError: `Setup exited ${setup.exitCode} (thread is still usable)`
2920
- });
2921
- }
2922
- } catch (err) {
2923
- const message = err instanceof Error ? err.message : String(err);
2924
- updateThread(thread.id, {
2925
- lastError: `Setup failed: ${message}`
2926
- });
2927
- }
2928
2941
  return readThread(thread.id) ?? thread;
2929
2942
  }
2930
2943
  async function listLinearIssues(agent, repoPath) {
2931
- const { getAdapter: getAdapter2 } = await import("./agents-3P4N6KHC.js");
2944
+ const { getAdapter: getAdapter2 } = await import("./agents-YKSS6VBO.js");
2932
2945
  await requireAgent(agent, { requireLinear: true });
2933
2946
  const adapter = getAdapter2(agent);
2934
2947
  if (!adapter.listLinearIssues) {
@@ -2971,7 +2984,9 @@ function worktreeBindingFrom(from) {
2971
2984
  sourceIsFork: from.sourceIsFork,
2972
2985
  parentThreadId: from.parentThreadId,
2973
2986
  prUrl: from.prUrl,
2974
- prTitle: from.prTitle
2987
+ prTitle: from.prTitle,
2988
+ stackId: from.stackId,
2989
+ stackLayer: from.stackLayer
2975
2990
  };
2976
2991
  }
2977
2992
  function threadsSharingWorktree(worktreePath) {
@@ -3084,11 +3099,305 @@ async function forkThreadWorktree(input, onSetupLine) {
3084
3099
  return thread;
3085
3100
  }
3086
3101
 
3102
+ // src/threads/stack-layers.ts
3103
+ import { existsSync as existsSync8 } from "fs";
3104
+ function stackIdFrom(stack) {
3105
+ if (stack.stackNumber != null) return `gh-stack-${stack.stackNumber}`;
3106
+ const key = stack.layers.map((l) => l.branchName).join("|");
3107
+ if (!key) return null;
3108
+ return `gh-stack-local-${hashShort(key)}`;
3109
+ }
3110
+ function hashShort(s) {
3111
+ let h = 0;
3112
+ for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) | 0;
3113
+ return Math.abs(h).toString(36);
3114
+ }
3115
+ function requireThread3(idOrRef) {
3116
+ const thread = findThreadByRef(idOrRef) ?? readThread(idOrRef);
3117
+ if (!thread) throw new Error(`Thread not found: ${idOrRef}`);
3118
+ return thread;
3119
+ }
3120
+ function sanitizeSlugPart(name) {
3121
+ return name.trim().replace(/^refs\/heads\//, "").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "layer";
3122
+ }
3123
+ function layerSlug(stackId, layer) {
3124
+ const stackPart = stackId.replace(/^gh-stack-/, "s");
3125
+ return sanitizeSlugPart(`${stackPart}-L${layer.position}-${layer.branchName}`);
3126
+ }
3127
+ function findThreadForStackLayer(repoPath, stackId, layer) {
3128
+ const threads = listThreads({ includeArchived: false }).filter(
3129
+ (t) => t.repoPath === repoPath && t.stackId === stackId
3130
+ );
3131
+ const byLayer = threads.find((t) => t.stackLayer === layer.position);
3132
+ if (byLayer) return byLayer;
3133
+ return threads.find((t) => t.branchName === layer.branchName) ?? listThreads({ includeArchived: false }).find(
3134
+ (t) => t.repoPath === repoPath && t.branchName === layer.branchName
3135
+ ) ?? null;
3136
+ }
3137
+ async function openStackLayer(input, _onSetupLine) {
3138
+ await requireAgent(input.agent);
3139
+ const repoPath = await resolveRepoRoot(input.repoPath);
3140
+ const stackId = stackIdFrom(input.stack);
3141
+ if (!stackId) throw new Error("Cannot open stack layer without a stack id");
3142
+ const existing = findThreadForStackLayer(repoPath, stackId, input.layer);
3143
+ if (existing) {
3144
+ const patch = {};
3145
+ if (existing.stackId !== stackId) patch.stackId = stackId;
3146
+ if (existing.stackLayer !== input.layer.position) {
3147
+ patch.stackLayer = input.layer.position;
3148
+ }
3149
+ if (input.layer.prUrl && existing.prUrl !== input.layer.prUrl) {
3150
+ patch.prUrl = input.layer.prUrl;
3151
+ }
3152
+ if (input.layer.title && existing.prTitle !== input.layer.title) {
3153
+ patch.prTitle = input.layer.title;
3154
+ }
3155
+ if (Object.keys(patch).length > 0) updateThread(existing.id, patch);
3156
+ return {
3157
+ thread: readThread(existing.id) ?? existing,
3158
+ createdWorktree: false
3159
+ };
3160
+ }
3161
+ let worktreePath = null;
3162
+ let branchName = input.layer.branchName;
3163
+ let createdWorktree = false;
3164
+ const trees = await listWorktrees(repoPath);
3165
+ const checkedOut = trees.find((w) => w.branch === branchName);
3166
+ if (checkedOut?.path && existsSync8(checkedOut.path)) {
3167
+ if (input.reuseExistingWorktree !== false) {
3168
+ worktreePath = checkedOut.path;
3169
+ } else {
3170
+ throw new Error(
3171
+ `Branch ${branchName} is already checked out at ${checkedOut.path}`
3172
+ );
3173
+ }
3174
+ }
3175
+ if (!worktreePath) {
3176
+ const slug = layerSlug(stackId, input.layer);
3177
+ const created = await createExistingBranchWorktree({
3178
+ repoPath,
3179
+ branchName,
3180
+ slug
3181
+ });
3182
+ worktreePath = created.worktreePath;
3183
+ branchName = created.branchName;
3184
+ copyConfiguredFiles(repoPath, worktreePath);
3185
+ createdWorktree = true;
3186
+ }
3187
+ const title = input.layer.title?.trim() || (input.layer.prNumber != null ? `PR #${input.layer.prNumber}` : input.layer.branchName);
3188
+ const thread = createEmptyThread({
3189
+ title,
3190
+ userSetTitle: Boolean(input.layer.title?.trim()),
3191
+ sourceType: input.layer.prNumber != null ? "pr" : "branch",
3192
+ sourceRef: input.layer.prNumber != null ? String(input.layer.prNumber) : input.layer.branchName,
3193
+ branchName,
3194
+ worktreePath,
3195
+ repoPath,
3196
+ agent: input.agent,
3197
+ autonomy: input.autonomy ?? "default",
3198
+ model: input.model ?? null,
3199
+ effort: input.effort ?? "high",
3200
+ fast: Boolean(input.fast),
3201
+ planMode: Boolean(input.planMode),
3202
+ parentThreadId: input.parentThreadId ?? null,
3203
+ status: "idle",
3204
+ prUrl: input.layer.prUrl,
3205
+ prTitle: input.layer.title ?? null,
3206
+ stackId,
3207
+ stackLayer: input.layer.position
3208
+ });
3209
+ writeThread(thread);
3210
+ await ensureWorkspace(repoPath);
3211
+ return { thread: readThread(thread.id) ?? thread, createdWorktree };
3212
+ }
3213
+ async function openPrStackLayers(input, onSetupLine) {
3214
+ const from = requireThread3(input.threadRef);
3215
+ if (!from.worktreePath?.trim() || !from.repoPath?.trim()) {
3216
+ throw new Error("Thread has no worktree");
3217
+ }
3218
+ const stack = await getPrStack(from.worktreePath);
3219
+ if (!stack) throw new Error("Current branch is not part of a GitHub PR stack");
3220
+ const layers = input.layer != null ? stack.layers.filter((l) => l.position === input.layer) : stack.layers;
3221
+ if (!layers.length) {
3222
+ throw new Error(
3223
+ input.layer != null ? `No stack layer at position ${input.layer}` : "Stack has no layers"
3224
+ );
3225
+ }
3226
+ const threads = [];
3227
+ const createdThreadIds = [];
3228
+ for (const layer of layers) {
3229
+ const { thread, createdWorktree } = await openStackLayer(
3230
+ {
3231
+ repoPath: from.repoPath,
3232
+ stack,
3233
+ layer,
3234
+ agent: from.agent,
3235
+ autonomy: from.autonomy,
3236
+ model: from.model,
3237
+ effort: from.effort,
3238
+ fast: from.fast,
3239
+ planMode: from.planMode,
3240
+ parentThreadId: from.id
3241
+ },
3242
+ onSetupLine
3243
+ );
3244
+ threads.push(thread);
3245
+ if (createdWorktree) createdThreadIds.push(thread.id);
3246
+ }
3247
+ const stackId = stackIdFrom(stack);
3248
+ const current = stack.layers[stack.currentIndex];
3249
+ if (stackId) {
3250
+ updateThread(from.id, {
3251
+ stackId,
3252
+ stackLayer: current?.position ?? from.stackLayer
3253
+ });
3254
+ }
3255
+ return { stack, threads, createdThreadIds };
3256
+ }
3257
+ async function addStackLayerFromThread(input, onSetupLine) {
3258
+ const from = requireThread3(input.threadRef);
3259
+ if (!from.worktreePath?.trim() || !from.repoPath?.trim()) {
3260
+ throw new Error("Thread has no worktree");
3261
+ }
3262
+ const status = await detectGhStack(from.worktreePath);
3263
+ if (!status.available) throw new Error(status.reason);
3264
+ await addPrStackLayer(from.worktreePath, input.branchName);
3265
+ const stack = await getPrStack(from.worktreePath);
3266
+ if (!stack) throw new Error("Stack not found after adding layer");
3267
+ const layer = stack.layers.find((l) => l.branchName === input.branchName.trim()) ?? stack.layers[stack.layers.length - 1];
3268
+ if (!layer) throw new Error("New stack layer not found");
3269
+ if (input.title?.trim()) {
3270
+ layer.title = input.title.trim();
3271
+ }
3272
+ const thread = await openStackLayer(
3273
+ {
3274
+ repoPath: from.repoPath,
3275
+ stack,
3276
+ layer,
3277
+ agent: from.agent,
3278
+ autonomy: from.autonomy,
3279
+ model: from.model,
3280
+ effort: from.effort,
3281
+ fast: from.fast,
3282
+ planMode: from.planMode,
3283
+ parentThreadId: from.id
3284
+ },
3285
+ onSetupLine
3286
+ );
3287
+ return { stack, thread: thread.thread, createdWorktree: thread.createdWorktree };
3288
+ }
3289
+ async function initStackFromThread(input, onSetupLine) {
3290
+ const from = requireThread3(input.threadRef);
3291
+ if (!from.worktreePath?.trim() || !from.branchName?.trim() || !from.repoPath?.trim()) {
3292
+ throw new Error("Thread has no worktree/branch");
3293
+ }
3294
+ const status = await detectGhStack(from.worktreePath);
3295
+ if (!status.available) throw new Error(status.reason);
3296
+ const branches = [
3297
+ from.branchName,
3298
+ ...(input.additionalBranches ?? []).map((b) => b.trim()).filter(Boolean)
3299
+ ];
3300
+ await initPrStack(from.worktreePath, branches, {
3301
+ base: input.base ?? await resolveDefaultBranch(from.repoPath)
3302
+ });
3303
+ return openPrStackLayers({ threadRef: from.id }, onSetupLine);
3304
+ }
3305
+ async function createPrStack(input, onSetupLine) {
3306
+ await requireAgent(input.agent);
3307
+ const repoPath = await resolveRepoRoot(input.repoPath);
3308
+ if (!existsSync8(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
3309
+ if (!input.branches.length) throw new Error("At least one branch name required");
3310
+ const status = await detectGhStack(repoPath);
3311
+ if (!status.available) throw new Error(status.reason);
3312
+ const team = allocateTeamSlug(repoPath);
3313
+ const base = input.base ?? await resolveDefaultBranch(repoPath);
3314
+ const bootstrap = await createThreadWorktree({
3315
+ repoPath,
3316
+ sourceRef: base,
3317
+ slug: `${team.slug}-stack-init`
3318
+ });
3319
+ copyConfiguredFiles(repoPath, bootstrap.worktreePath);
3320
+ try {
3321
+ await initPrStack(bootstrap.worktreePath, input.branches, { base });
3322
+ const bottom = input.branches[0];
3323
+ const co = await git(["checkout", bottom], bootstrap.worktreePath, {
3324
+ reject: false
3325
+ });
3326
+ if (co.exitCode !== 0) {
3327
+ throw new Error(
3328
+ co.stderr.trim() || co.stdout.trim() || `Could not check out ${bottom} after stack init`
3329
+ );
3330
+ }
3331
+ } catch (err) {
3332
+ try {
3333
+ await removeWorktree(repoPath, bootstrap.worktreePath, {
3334
+ deleteBranch: bootstrap.branchName
3335
+ });
3336
+ } catch {
3337
+ }
3338
+ throw err;
3339
+ }
3340
+ const stack = await getPrStack(bootstrap.worktreePath);
3341
+ if (!stack) {
3342
+ try {
3343
+ await removeWorktree(repoPath, bootstrap.worktreePath, {
3344
+ deleteBranch: bootstrap.branchName
3345
+ });
3346
+ } catch {
3347
+ }
3348
+ throw new Error("Stack init succeeded but gh stack view returned no stack");
3349
+ }
3350
+ const threads = [];
3351
+ const createdThreadIds = [];
3352
+ for (const layer of stack.layers) {
3353
+ const title = layer.position === 1 && input.title?.trim() ? input.title.trim() : layer.title;
3354
+ const opened = await openStackLayer(
3355
+ {
3356
+ repoPath,
3357
+ stack,
3358
+ layer: title ? { ...layer, title } : layer,
3359
+ agent: input.agent,
3360
+ autonomy: input.autonomy,
3361
+ model: input.model,
3362
+ effort: input.effort,
3363
+ fast: input.fast,
3364
+ planMode: input.planMode,
3365
+ reuseExistingWorktree: true
3366
+ },
3367
+ onSetupLine
3368
+ );
3369
+ threads.push(opened.thread);
3370
+ if (opened.createdWorktree || opened.thread.worktreePath === bootstrap.worktreePath) {
3371
+ createdThreadIds.push(opened.thread.id);
3372
+ }
3373
+ }
3374
+ const claimed = new Set(threads.map((t) => t.worktreePath));
3375
+ if (!claimed.has(bootstrap.worktreePath) && existsSync8(bootstrap.worktreePath)) {
3376
+ try {
3377
+ await removeWorktree(repoPath, bootstrap.worktreePath, {
3378
+ deleteBranch: bootstrap.branchName
3379
+ });
3380
+ } catch {
3381
+ }
3382
+ }
3383
+ return { stack, threads, createdThreadIds };
3384
+ }
3385
+ function stackAgentDefaultsFrom(input) {
3386
+ return {
3387
+ agent: input.agent,
3388
+ autonomy: input.autonomy,
3389
+ model: input.model,
3390
+ effort: input.effort,
3391
+ fast: input.fast,
3392
+ planMode: input.planMode
3393
+ };
3394
+ }
3395
+
3087
3396
  // src/threads/adopt.ts
3088
3397
  import { execFileSync } from "child_process";
3089
3398
  import {
3090
3399
  copyFileSync as copyFileSync3,
3091
- existsSync as existsSync8,
3400
+ existsSync as existsSync9,
3092
3401
  mkdtempSync,
3093
3402
  readdirSync as readdirSync3,
3094
3403
  readFileSync as readFileSync7,
@@ -3116,7 +3425,7 @@ function mapAgentType(raw) {
3116
3425
  return null;
3117
3426
  }
3118
3427
  function resolveConductorCursorAgentId(workspacePath) {
3119
- if (!workspacePath || !existsSync8(CURSOR_SDK_STORE)) return null;
3428
+ if (!workspacePath || !existsSync9(CURSOR_SDK_STORE)) return null;
3120
3429
  const normalized = workspacePath.replace(/\/$/, "");
3121
3430
  let best = null;
3122
3431
  let hashes;
@@ -3127,7 +3436,7 @@ function resolveConductorCursorAgentId(workspacePath) {
3127
3436
  }
3128
3437
  for (const hash of hashes) {
3129
3438
  const agentsFile = join7(CURSOR_SDK_STORE, hash, "agents.ndjson");
3130
- if (!existsSync8(agentsFile)) continue;
3439
+ if (!existsSync9(agentsFile)) continue;
3131
3440
  let text;
3132
3441
  try {
3133
3442
  text = readFileSync7(agentsFile, "utf8");
@@ -3154,11 +3463,11 @@ function resolveConductorCursorAgentId(workspacePath) {
3154
3463
  return best?.agentId ?? null;
3155
3464
  }
3156
3465
  async function adoptThread(input) {
3157
- if (!existsSync8(input.worktreePath)) {
3466
+ if (!existsSync9(input.worktreePath)) {
3158
3467
  throw new Error(`Worktree not found: ${input.worktreePath}`);
3159
3468
  }
3160
3469
  const repoPath = await resolveRepoRoot(input.worktreePath);
3161
- const { stdout: branch } = await import("./run-LF6E5IKL.js").then(
3470
+ const { stdout: branch } = await import("./run-ZJKYHXGN.js").then(
3162
3471
  ({ git: git2 }) => git2(["rev-parse", "--abbrev-ref", "HEAD"], input.worktreePath)
3163
3472
  );
3164
3473
  const thread = createEmptyThread({
@@ -3173,7 +3482,7 @@ async function adoptThread(input) {
3173
3482
  messages: input.messages ?? []
3174
3483
  });
3175
3484
  writeThread(thread);
3176
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-UJX7JIGH.js");
3485
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-AYTBR6KQ.js");
3177
3486
  await ensureWorkspace2(repoPath);
3178
3487
  return thread;
3179
3488
  }
@@ -3181,7 +3490,7 @@ function conductorDbPath() {
3181
3490
  return CONDUCTOR_DB;
3182
3491
  }
3183
3492
  function listConductorWorkspaces() {
3184
- if (!existsSync8(CONDUCTOR_DB)) {
3493
+ if (!existsSync9(CONDUCTOR_DB)) {
3185
3494
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
3186
3495
  }
3187
3496
  const tmp = mkdtempSync(join7(tmpdir(), "sideboard-conductor-"));
@@ -3190,7 +3499,7 @@ function listConductorWorkspaces() {
3190
3499
  copyFileSync3(CONDUCTOR_DB, snapshot);
3191
3500
  for (const suffix of ["-wal", "-shm"]) {
3192
3501
  const src = `${CONDUCTOR_DB}${suffix}`;
3193
- if (existsSync8(src)) {
3502
+ if (existsSync9(src)) {
3194
3503
  try {
3195
3504
  copyFileSync3(src, `${snapshot}${suffix}`);
3196
3505
  } catch {
@@ -3272,7 +3581,7 @@ function listConductorWorkspaces() {
3272
3581
  }
3273
3582
  }
3274
3583
  function importConductorWorkspace(workspaceId) {
3275
- if (!existsSync8(CONDUCTOR_DB)) {
3584
+ if (!existsSync9(CONDUCTOR_DB)) {
3276
3585
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
3277
3586
  }
3278
3587
  const tmp = mkdtempSync(join7(tmpdir(), "sideboard-conductor-"));
@@ -3281,7 +3590,7 @@ function importConductorWorkspace(workspaceId) {
3281
3590
  copyFileSync3(CONDUCTOR_DB, snapshot);
3282
3591
  for (const suffix of ["-wal", "-shm"]) {
3283
3592
  const src = `${CONDUCTOR_DB}${suffix}`;
3284
- if (existsSync8(src)) {
3593
+ if (existsSync9(src)) {
3285
3594
  try {
3286
3595
  copyFileSync3(src, `${snapshot}${suffix}`);
3287
3596
  } catch {
@@ -3301,7 +3610,7 @@ function importConductorWorkspace(workspaceId) {
3301
3610
  ).get(workspaceId);
3302
3611
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
3303
3612
  const worktreePath = String(row.workspacePath);
3304
- if (!existsSync8(worktreePath)) {
3613
+ if (!existsSync9(worktreePath)) {
3305
3614
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
3306
3615
  }
3307
3616
  let sessionId = null;
@@ -3373,10 +3682,10 @@ async function importConductorWorkspaceAsync(workspaceId) {
3373
3682
 
3374
3683
  // src/orchestrator/orchestrator.ts
3375
3684
  import { EventEmitter } from "events";
3376
- import { existsSync as existsSync12 } from "fs";
3685
+ import { existsSync as existsSync13 } from "fs";
3377
3686
 
3378
3687
  // src/git/orphan-cleanup.ts
3379
- import { existsSync as existsSync9, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
3688
+ import { existsSync as existsSync10, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
3380
3689
  import { join as join8 } from "path";
3381
3690
  function isSideboardWorktreePath(path) {
3382
3691
  return path.includes("/.sideboard/worktrees/") || path.includes("/sideboard/workspaces/");
@@ -3388,7 +3697,7 @@ async function findOrphanWorktrees(repoPaths) {
3388
3697
  repoPaths?.length ? repoPaths : threads.map((t) => t.repoPath).filter(Boolean)
3389
3698
  );
3390
3699
  const homeRoot = sideboardWorkspacesDir();
3391
- if (existsSync9(homeRoot)) {
3700
+ if (existsSync10(homeRoot)) {
3392
3701
  try {
3393
3702
  for (const entry of readdirSync4(homeRoot, { withFileTypes: true })) {
3394
3703
  if (!entry.isDirectory()) continue;
@@ -3400,7 +3709,7 @@ async function findOrphanWorktrees(repoPaths) {
3400
3709
  const orphans = [];
3401
3710
  const seen = /* @__PURE__ */ new Set();
3402
3711
  for (const repoPath of repos) {
3403
- if (!repoPath || !existsSync9(repoPath)) continue;
3712
+ if (!repoPath || !existsSync10(repoPath)) continue;
3404
3713
  try {
3405
3714
  const wts = await listWorktrees(repoPath);
3406
3715
  for (const wt of wts) {
@@ -3421,12 +3730,12 @@ async function findOrphanWorktrees(repoPaths) {
3421
3730
  }
3422
3731
  try {
3423
3732
  const root = worktreesRoot(repoPath);
3424
- if (existsSync9(root)) {
3733
+ if (existsSync10(root)) {
3425
3734
  for (const entry of readdirSync4(root, { withFileTypes: true })) {
3426
3735
  if (!entry.isDirectory()) continue;
3427
3736
  const path = join8(root, entry.name).replace(/\/$/, "");
3428
3737
  if (known.has(path) || seen.has(path)) continue;
3429
- if (!existsSync9(join8(path, ".git"))) continue;
3738
+ if (!existsSync10(join8(path, ".git"))) continue;
3430
3739
  seen.add(path);
3431
3740
  let mtimeMs = 0;
3432
3741
  try {
@@ -3575,7 +3884,7 @@ async function applyThreadIntoMain(thread, opts) {
3575
3884
  }
3576
3885
 
3577
3886
  // src/git/clone-repo.ts
3578
- import { existsSync as existsSync10 } from "fs";
3887
+ import { existsSync as existsSync11 } from "fs";
3579
3888
  import { basename as basename3, join as join9 } from "path";
3580
3889
  import { execa as execa5 } from "execa";
3581
3890
  async function cloneRepoIntoSideboard(opts) {
@@ -3588,7 +3897,7 @@ async function cloneRepoIntoSideboard(opts) {
3588
3897
  }
3589
3898
  name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
3590
3899
  const dest = join9(sideboardReposDir(), name);
3591
- if (existsSync10(dest)) {
3900
+ if (existsSync11(dest)) {
3592
3901
  const repoPath2 = await resolveRepoRoot(dest);
3593
3902
  const workspace2 = await ensureWorkspace(repoPath2);
3594
3903
  return { repoPath: repoPath2, workspace: workspace2 };
@@ -3606,7 +3915,7 @@ async function cloneRepoIntoSideboard(opts) {
3606
3915
 
3607
3916
  // src/review/request-review.ts
3608
3917
  import { randomUUID as randomUUID4 } from "crypto";
3609
- import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
3918
+ import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
3610
3919
  import { dirname as dirname3, join as join10 } from "path";
3611
3920
 
3612
3921
  // src/review/review-request-template.ts
@@ -3731,13 +4040,10 @@ File: src/client/frontends/desktop/core/UserData.ts
3731
4040
  // src/review/request-review.ts
3732
4041
  var REPO_REVIEW_PATH = ".sideboard/review.md";
3733
4042
  var REPO_REVIEW_NAME = "review.md";
3734
- var REVIEW_REQUEST_PATH = ".sideboard/attachments/Review request.md";
4043
+ var REVIEW_REQUEST_PATH = `${ATTACHMENTS_DIR}/Review request.md`;
4044
+ var LEGACY_REVIEW_REQUEST_PATH = `${LEGACY_ATTACHMENTS_DIR}/Review request.md`;
3735
4045
  var REVIEW_REQUEST_NAME = "Review request.md";
3736
4046
  var REVIEW_REQUEST_PREFILL = "Review.";
3737
- var ATTACHMENTS_GITIGNORE2 = `# Sideboard review / composer attachments (local only)
3738
- *
3739
- !.gitignore
3740
- `;
3741
4047
  var LEGACY_REVIEW_TEMPLATE_MARKERS = [
3742
4048
  "You are acting as a reviewer for a proposed code change made by another engineer.",
3743
4049
  "HOW MANY FINDINGS TO RETURN:"
@@ -3751,7 +4057,7 @@ function shouldRefreshReviewRequestTemplate(content) {
3751
4057
  return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
3752
4058
  }
3753
4059
  function readTextIfPresent(abs) {
3754
- if (!existsSync11(abs)) return null;
4060
+ if (!existsSync12(abs)) return null;
3755
4061
  try {
3756
4062
  const content = readFileSync8(abs, "utf8");
3757
4063
  return content.trim() ? content : null;
@@ -3760,10 +4066,10 @@ function readTextIfPresent(abs) {
3760
4066
  }
3761
4067
  }
3762
4068
  function ensureAttachmentsGitignore(worktreePath) {
3763
- const gitignoreAbs = join10(worktreePath, ".sideboard", "attachments", ".gitignore");
3764
- if (existsSync11(gitignoreAbs)) return;
4069
+ const gitignoreAbs = join10(worktreePath, ATTACHMENTS_DIR, ".gitignore");
4070
+ if (existsSync12(gitignoreAbs)) return;
3765
4071
  mkdirSync4(dirname3(gitignoreAbs), { recursive: true });
3766
- writeFileSync3(gitignoreAbs, ATTACHMENTS_GITIGNORE2, "utf8");
4072
+ writeFileSync3(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
3767
4073
  }
3768
4074
  function resolveReviewGuidelines(worktreePath) {
3769
4075
  const repoAbs = join10(worktreePath, REPO_REVIEW_PATH);
@@ -3786,6 +4092,16 @@ function resolveReviewGuidelines(worktreePath) {
3786
4092
  source: "local"
3787
4093
  };
3788
4094
  }
4095
+ const legacyAbs = join10(worktreePath, LEGACY_REVIEW_REQUEST_PATH);
4096
+ const legacyContent = readTextIfPresent(legacyAbs);
4097
+ if (legacyContent && !shouldRefreshReviewRequestTemplate(legacyContent)) {
4098
+ return {
4099
+ path: LEGACY_REVIEW_REQUEST_PATH,
4100
+ name: REVIEW_REQUEST_NAME,
4101
+ content: legacyContent,
4102
+ source: "local"
4103
+ };
4104
+ }
3789
4105
  ensureAttachmentsGitignore(worktreePath);
3790
4106
  mkdirSync4(dirname3(localAbs), { recursive: true });
3791
4107
  writeFileSync3(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
@@ -3808,10 +4124,11 @@ function ensureReviewRequestFile(worktreePath) {
3808
4124
  };
3809
4125
  }
3810
4126
  const localAbs = join10(worktreePath, REVIEW_REQUEST_PATH);
3811
- const localContent = readTextIfPresent(localAbs);
4127
+ const localContent = readTextIfPresent(localAbs) ?? readTextIfPresent(join10(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
3812
4128
  if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
4129
+ const path = existsSync12(localAbs) ? REVIEW_REQUEST_PATH : LEGACY_REVIEW_REQUEST_PATH;
3813
4130
  return {
3814
- path: REVIEW_REQUEST_PATH,
4131
+ path,
3815
4132
  name: REVIEW_REQUEST_NAME,
3816
4133
  content: localContent,
3817
4134
  source: "local"
@@ -3838,7 +4155,7 @@ function buildReviewRequestAttachment(content, opts) {
3838
4155
  };
3839
4156
  }
3840
4157
  function readExistingReviewRequestFile(worktreePath) {
3841
- return readTextIfPresent(join10(worktreePath, REPO_REVIEW_PATH)) ?? readTextIfPresent(join10(worktreePath, REVIEW_REQUEST_PATH));
4158
+ return readTextIfPresent(join10(worktreePath, REPO_REVIEW_PATH)) ?? readTextIfPresent(join10(worktreePath, REVIEW_REQUEST_PATH)) ?? readTextIfPresent(join10(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
3842
4159
  }
3843
4160
  async function requestReview(threadRef, send) {
3844
4161
  const from = findThreadByRef(threadRef);
@@ -4082,7 +4399,7 @@ var Orchestrator = class {
4082
4399
  }
4083
4400
  continue;
4084
4401
  }
4085
- if (!existsSync12(thread.worktreePath)) {
4402
+ if (!existsSync13(thread.worktreePath)) {
4086
4403
  setStatus(thread.id, "broken", "Worktree missing on disk");
4087
4404
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
4088
4405
  continue;
@@ -4227,14 +4544,9 @@ var Orchestrator = class {
4227
4544
  return findThreadByRef(idOrRef) ?? readThread(idOrRef);
4228
4545
  }
4229
4546
  async createThread(input) {
4230
- let thread = await createThread(input, (line) => {
4231
- this.emit({
4232
- type: "turn_output",
4233
- threadId: "pending",
4234
- event: { type: "stdout", data: line }
4235
- });
4236
- });
4547
+ let thread = await createThread(input);
4237
4548
  this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
4549
+ await this.runSetupAfterCreate(thread.id);
4238
4550
  const { autoRunAfterSetupEnabled: autoRunAfterSetupEnabled2 } = await import("./app-settings-LZP632KI.js");
4239
4551
  if (autoRunAfterSetupEnabled2()) {
4240
4552
  try {
@@ -4248,6 +4560,19 @@ var Orchestrator = class {
4248
4560
  }
4249
4561
  return thread;
4250
4562
  }
4563
+ /** Run workspace setup after a new worktree is created (no-op if none configured). */
4564
+ async runSetupAfterCreate(threadId) {
4565
+ try {
4566
+ await this.runSetup(threadId);
4567
+ } catch (err) {
4568
+ const message = err instanceof Error ? err.message : String(err);
4569
+ if (/no setup script/i.test(message)) return;
4570
+ if (/already running/i.test(message)) return;
4571
+ updateThread(threadId, {
4572
+ lastError: `Setup failed: ${message}`
4573
+ });
4574
+ }
4575
+ }
4251
4576
  listWorkspaces() {
4252
4577
  const fromThreads = listThreads({ includeArchived: false }).map((t) => t.repoPath);
4253
4578
  return syncWorkspacesFromThreads(fromThreads);
@@ -4596,6 +4921,19 @@ var Orchestrator = class {
4596
4921
  });
4597
4922
  }
4598
4923
  const afterTurn = this.requireThread(threadId);
4924
+ if (afterTurn.planMode && afterTurn.worktreePath?.trim()) {
4925
+ const presented = extractPresentedPlan(parts);
4926
+ const exited = parts.some(
4927
+ (p) => p.type === "tool" && /exitplanmode/i.test(p.name)
4928
+ );
4929
+ if (presented?.content) {
4930
+ writePlanFile(afterTurn.worktreePath, presented.content);
4931
+ } else if (exited || chatText && chatText.trim().length >= 400) {
4932
+ if (!readPlanFile(afterTurn.worktreePath) && chatText?.trim()) {
4933
+ writePlanFile(afterTurn.worktreePath, chatText.trim());
4934
+ }
4935
+ }
4936
+ }
4599
4937
  if (afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
4600
4938
  (p) => p.type === "tool" && /exitplanmode/i.test(p.name)
4601
4939
  )) {
@@ -5077,6 +5415,82 @@ var Orchestrator = class {
5077
5415
  }
5078
5416
  return meta;
5079
5417
  }
5418
+ async getPrStack(threadRef) {
5419
+ const thread = this.requireThread(threadRef);
5420
+ if (!thread.worktreePath?.trim()) return null;
5421
+ const stack = await getPrStack(thread.worktreePath);
5422
+ if (!stack) return null;
5423
+ const current = stack.currentIndex >= 0 ? stack.layers[stack.currentIndex] : null;
5424
+ const patch = {};
5425
+ if (stack.stackNumber != null) {
5426
+ const id = `gh-stack-${stack.stackNumber}`;
5427
+ if (thread.stackId !== id) patch.stackId = id;
5428
+ }
5429
+ if (current?.position != null && thread.stackLayer !== current.position) {
5430
+ patch.stackLayer = current.position;
5431
+ }
5432
+ if (current?.prUrl && current.prUrl !== thread.prUrl) patch.prUrl = current.prUrl;
5433
+ if (current?.title && current.title !== thread.prTitle) patch.prTitle = current.title;
5434
+ if (current?.branchName && current.branchName !== thread.branchName) {
5435
+ patch.branchName = current.branchName;
5436
+ }
5437
+ if (Object.keys(patch).length > 0) updateThread(thread.id, patch);
5438
+ return stack;
5439
+ }
5440
+ /** Open worktrees for all (or one) stack layers discovered from a thread. */
5441
+ async openPrStackLayers(threadRef, opts) {
5442
+ const result = await openPrStackLayers({ threadRef, layer: opts?.layer });
5443
+ for (const t of result.threads) {
5444
+ this.emit({ type: "status_changed", threadId: t.id, status: t.status });
5445
+ }
5446
+ for (const id of result.createdThreadIds) {
5447
+ await this.runSetupAfterCreate(id);
5448
+ }
5449
+ return { stack: result.stack, threads: result.threads };
5450
+ }
5451
+ /** Add a branch on top of the thread's stack and open its worktree. */
5452
+ async addStackLayer(threadRef, branchName, opts) {
5453
+ const result = await addStackLayerFromThread({
5454
+ threadRef,
5455
+ branchName,
5456
+ title: opts?.title
5457
+ });
5458
+ this.emit({
5459
+ type: "status_changed",
5460
+ threadId: result.thread.id,
5461
+ status: result.thread.status
5462
+ });
5463
+ if (result.createdWorktree) {
5464
+ await this.runSetupAfterCreate(result.thread.id);
5465
+ }
5466
+ return { stack: result.stack, thread: result.thread };
5467
+ }
5468
+ /** Initialize a stack from the current thread branch (optional extra layers). */
5469
+ async initStackFromThread(threadRef, opts) {
5470
+ const result = await initStackFromThread({
5471
+ threadRef,
5472
+ additionalBranches: opts?.additionalBranches,
5473
+ base: opts?.base
5474
+ });
5475
+ for (const t of result.threads) {
5476
+ this.emit({ type: "status_changed", threadId: t.id, status: t.status });
5477
+ }
5478
+ for (const id of result.createdThreadIds) {
5479
+ await this.runSetupAfterCreate(id);
5480
+ }
5481
+ return { stack: result.stack, threads: result.threads };
5482
+ }
5483
+ /** Create a new multi-layer stack with one worktree per layer. */
5484
+ async createPrStack(input) {
5485
+ const result = await createPrStack(input);
5486
+ for (const t of result.threads) {
5487
+ this.emit({ type: "status_changed", threadId: t.id, status: t.status });
5488
+ }
5489
+ for (const id of result.createdThreadIds) {
5490
+ await this.runSetupAfterCreate(id);
5491
+ }
5492
+ return { stack: result.stack, threads: result.threads };
5493
+ }
5080
5494
  async getPrDetails(threadRef) {
5081
5495
  const { thread, selector, cwd } = await this.withPrSelector(threadRef);
5082
5496
  if (!selector) return null;
@@ -5139,14 +5553,9 @@ var Orchestrator = class {
5139
5553
  return forkChatTab(input);
5140
5554
  }
5141
5555
  async forkThreadWorktree(input) {
5142
- const thread = await forkThreadWorktree(input, (line) => {
5143
- this.emit({
5144
- type: "turn_output",
5145
- threadId: "pending",
5146
- event: { type: "stdout", data: line }
5147
- });
5148
- });
5556
+ const thread = await forkThreadWorktree(input);
5149
5557
  this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
5558
+ await this.runSetupAfterCreate(thread.id);
5150
5559
  return thread;
5151
5560
  }
5152
5561
  renameThread(threadRef, title) {
@@ -5160,7 +5569,7 @@ var Orchestrator = class {
5160
5569
  }
5161
5570
  /**
5162
5571
  * Stage OS / worktree files into composer attachments (copies external files
5163
- * into `.sideboard/attachments/` so agents can Read images and binaries).
5572
+ * into `.context/attachments/` so agents can Read images and binaries).
5164
5573
  */
5165
5574
  attachComposerFiles(threadRef, opts) {
5166
5575
  const thread = this.requireThread(threadRef);
@@ -5234,8 +5643,8 @@ var Orchestrator = class {
5234
5643
  updateThread(thread.id, { worktreePath: globalAgentCwd2() });
5235
5644
  return setStatus(thread.id, "idle");
5236
5645
  }
5237
- if (!existsSync12(thread.worktreePath)) {
5238
- const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-TEDAJ57S.js");
5646
+ if (!existsSync13(thread.worktreePath)) {
5647
+ const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-RWGL7FUV.js");
5239
5648
  const { execa: execa6 } = await import("execa");
5240
5649
  const slug = thread.worktreePath.split("/").pop();
5241
5650
  const dest = thread.worktreePath;
@@ -5320,7 +5729,7 @@ async function startOrchestration(opts) {
5320
5729
  sourceRef: "default",
5321
5730
  ...createOpts
5322
5731
  }).catch(async () => {
5323
- const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-TEDAJ57S.js");
5732
+ const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-RWGL7FUV.js");
5324
5733
  const repo = await resolveRepoRoot2(repoPath);
5325
5734
  const def = await resolveDefaultBranch2(repo);
5326
5735
  return createThread({
@@ -5330,7 +5739,7 @@ async function startOrchestration(opts) {
5330
5739
  repoPath: repo
5331
5740
  });
5332
5741
  });
5333
- const { updateThread: upd } = await import("./thread-store-OV2X6PYO.js");
5742
+ const { updateThread: upd } = await import("./thread-store-UJIGMI5J.js");
5334
5743
  const updated = upd(thread.id, {
5335
5744
  sourceType: "orchestration",
5336
5745
  sourceRef: goal
@@ -5341,6 +5750,104 @@ async function startOrchestration(opts) {
5341
5750
  return updated;
5342
5751
  }
5343
5752
 
5753
+ // src/plan/ask-user.ts
5754
+ function asRecord2(v) {
5755
+ return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
5756
+ }
5757
+ function parseOptions(raw) {
5758
+ if (!Array.isArray(raw)) return [];
5759
+ const out = [];
5760
+ for (const item of raw) {
5761
+ if (typeof item === "string" && item.trim()) {
5762
+ out.push({ label: item.trim() });
5763
+ continue;
5764
+ }
5765
+ const o = asRecord2(item);
5766
+ if (!o) continue;
5767
+ const label = typeof o.label === "string" ? o.label.trim() : typeof o.text === "string" ? o.text.trim() : typeof o.title === "string" ? o.title.trim() : "";
5768
+ if (!label) continue;
5769
+ const description = typeof o.description === "string" && o.description.trim() ? o.description.trim() : void 0;
5770
+ out.push({ label, description });
5771
+ }
5772
+ return out;
5773
+ }
5774
+ function parseOneQuestion(raw) {
5775
+ const o = asRecord2(raw);
5776
+ if (!o) return null;
5777
+ const question = typeof o.question === "string" ? o.question.trim() : typeof o.text === "string" ? o.text.trim() : typeof o.prompt === "string" ? o.prompt.trim() : "";
5778
+ if (!question) return null;
5779
+ const options = parseOptions(o.options);
5780
+ if (options.length < 1) return null;
5781
+ const header = typeof o.header === "string" && o.header.trim() ? o.header.trim() : void 0;
5782
+ const multiSelect = Boolean(o.multiSelect ?? o.multi_select);
5783
+ return { question, header, multiSelect, options };
5784
+ }
5785
+ function parsePlanQuestionsInput(input) {
5786
+ const root = asRecord2(input);
5787
+ if (!root) return [];
5788
+ const list = Array.isArray(root.questions) ? root.questions : Array.isArray(root.question) ? root.question : root.question || root.prompt ? [root] : [];
5789
+ const out = [];
5790
+ for (const item of list) {
5791
+ const q = parseOneQuestion(item);
5792
+ if (q) out.push(q);
5793
+ }
5794
+ return out;
5795
+ }
5796
+ var ASK_USER_TOOL_RE = /^(AskUserQuestion|ask_user|mcp__sideboard__ask_user)$/i;
5797
+ function isAskUserToolName(name) {
5798
+ if (!name) return false;
5799
+ const base = name.replace(/^mcp__sideboard__/i, "");
5800
+ return ASK_USER_TOOL_RE.test(name) || /^ask_user$/i.test(base);
5801
+ }
5802
+ function extractPendingPlanQuestions(parts) {
5803
+ if (!parts?.length) return null;
5804
+ for (let i = parts.length - 1; i >= 0; i--) {
5805
+ const p = parts[i];
5806
+ if (p.type !== "tool" || !isAskUserToolName(p.name)) continue;
5807
+ const questions = parsePlanQuestionsInput(p.input);
5808
+ if (!questions.length) continue;
5809
+ return {
5810
+ id: p.id || `ask-${i}`,
5811
+ questions,
5812
+ source: p.name || "ask_user"
5813
+ };
5814
+ }
5815
+ return null;
5816
+ }
5817
+ function formatPlanQuestionAnswers(questions, answers) {
5818
+ const lines = ["Answers to your questions:"];
5819
+ for (let i = 0; i < questions.length; i++) {
5820
+ const q = questions[i];
5821
+ const a = answers.find((x) => x.questionIndex === i);
5822
+ const header = q.header ? `**${q.header}** \u2014 ` : "";
5823
+ const parts = [];
5824
+ if (a?.selected.length) parts.push(a.selected.join(", "));
5825
+ if (a?.other?.trim()) parts.push(a.other.trim());
5826
+ const body = parts.length ? parts.join(" \xB7 ") : "(no answer)";
5827
+ lines.push(`${i + 1}. ${header}${q.question}`);
5828
+ lines.push(` \u2192 ${body}`);
5829
+ }
5830
+ return lines.join("\n");
5831
+ }
5832
+ function formatPlanQuestionsForChat(questions) {
5833
+ const lines = ["### Questions for you", ""];
5834
+ for (let i = 0; i < questions.length; i++) {
5835
+ const q = questions[i];
5836
+ const header = q.header ? `**${q.header}** \u2014 ` : "";
5837
+ lines.push(`${i + 1}. ${header}${q.question}`);
5838
+ for (let oi = 0; oi < q.options.length; oi++) {
5839
+ const opt = q.options[oi];
5840
+ const desc = opt.description?.trim();
5841
+ lines.push(
5842
+ desc ? ` - **${opt.label}** \u2014 ${desc}` : ` - **${opt.label}**`
5843
+ );
5844
+ }
5845
+ lines.push("");
5846
+ }
5847
+ lines.push("_Answer in the composer below._");
5848
+ return lines.join("\n").trimEnd();
5849
+ }
5850
+
5344
5851
  // src/mcp/server.ts
5345
5852
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5346
5853
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -5452,6 +5959,59 @@ async function startMcpServer() {
5452
5959
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
5453
5960
  }
5454
5961
  );
5962
+ server.tool(
5963
+ "ask_user",
5964
+ "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.",
5965
+ {
5966
+ questions: z.array(
5967
+ z.object({
5968
+ question: z.string().describe("Full question text ending with ?"),
5969
+ header: z.string().max(24).optional().describe("Short label shown above the question"),
5970
+ multiSelect: z.boolean().optional().describe("Allow selecting multiple options"),
5971
+ options: z.array(
5972
+ z.object({
5973
+ label: z.string(),
5974
+ description: z.string().optional().describe("What this option means / when to choose it (strongly preferred)")
5975
+ })
5976
+ ).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
5977
+ })
5978
+ ).min(1).max(4).describe("1\u20134 questions")
5979
+ },
5980
+ async ({ questions }) => {
5981
+ const payload = {
5982
+ ok: true,
5983
+ questions,
5984
+ message: "Questions shown in Sideboard\u2019s composer. Wait for the user\u2019s next message with their answers before continuing."
5985
+ };
5986
+ return { content: [{ type: "text", text: JSON.stringify(payload) }] };
5987
+ }
5988
+ );
5989
+ server.tool(
5990
+ "present_plan",
5991
+ "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.",
5992
+ {
5993
+ title: z.string().optional().describe("Short plan title (defaults to Plan)"),
5994
+ content: z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
5995
+ thread_id: z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
5996
+ },
5997
+ async ({ title, content, thread_id }) => {
5998
+ const { writePlanFile: writePlanFile2 } = await import("./plan-file-6O7G4VPQ.js");
5999
+ let root = process.cwd();
6000
+ if (thread_id?.trim()) {
6001
+ const t = orch.getThread(thread_id.trim());
6002
+ if (t?.worktreePath?.trim()) root = t.worktreePath;
6003
+ }
6004
+ const path = writePlanFile2(root, content);
6005
+ const payload = {
6006
+ ok: true,
6007
+ path,
6008
+ title: title?.trim() || "Plan",
6009
+ content,
6010
+ message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
6011
+ };
6012
+ return { content: [{ type: "text", text: JSON.stringify(payload) }] };
6013
+ }
6014
+ );
5455
6015
  server.tool(
5456
6016
  "present_schema",
5457
6017
  "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.",
@@ -6030,6 +6590,126 @@ async function startMcpServer() {
6030
6590
  };
6031
6591
  }
6032
6592
  );
6593
+ server.tool(
6594
+ "get_pr_stack",
6595
+ "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.",
6596
+ { ref: z.string() },
6597
+ async ({ ref }) => {
6598
+ const stack = await orch.getPrStack(ref);
6599
+ return {
6600
+ content: [{ type: "text", text: JSON.stringify(stack, null, 2) }]
6601
+ };
6602
+ }
6603
+ );
6604
+ server.tool(
6605
+ "open_pr_stack_layers",
6606
+ "Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
6607
+ {
6608
+ ref: z.string(),
6609
+ layer: z.number().int().positive().optional()
6610
+ },
6611
+ async ({ ref, layer }) => {
6612
+ const result = await orch.openPrStackLayers(ref, { layer });
6613
+ return {
6614
+ content: [
6615
+ {
6616
+ type: "text",
6617
+ text: JSON.stringify(
6618
+ {
6619
+ stackNumber: result.stack.stackNumber,
6620
+ trunk: result.stack.trunk,
6621
+ threads: result.threads.map((t) => ({
6622
+ id: t.id,
6623
+ title: t.title,
6624
+ branchName: t.branchName,
6625
+ stackLayer: t.stackLayer,
6626
+ worktreePath: t.worktreePath,
6627
+ prUrl: t.prUrl,
6628
+ link: `sideboard://thread/${t.id}`
6629
+ }))
6630
+ },
6631
+ null,
6632
+ 2
6633
+ )
6634
+ }
6635
+ ]
6636
+ };
6637
+ }
6638
+ );
6639
+ server.tool(
6640
+ "add_stack_layer",
6641
+ "Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
6642
+ {
6643
+ ref: z.string(),
6644
+ branchName: z.string(),
6645
+ title: z.string().optional()
6646
+ },
6647
+ async ({ ref, branchName, title }) => {
6648
+ const result = await orch.addStackLayer(ref, branchName, { title });
6649
+ return {
6650
+ content: [
6651
+ {
6652
+ type: "text",
6653
+ text: JSON.stringify(
6654
+ {
6655
+ id: result.thread.id,
6656
+ title: result.thread.title,
6657
+ branchName: result.thread.branchName,
6658
+ stackLayer: result.thread.stackLayer,
6659
+ worktreePath: result.thread.worktreePath,
6660
+ link: `sideboard://thread/${result.thread.id}`
6661
+ },
6662
+ null,
6663
+ 2
6664
+ )
6665
+ }
6666
+ ]
6667
+ };
6668
+ }
6669
+ );
6670
+ server.tool(
6671
+ "create_pr_stack",
6672
+ "Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
6673
+ {
6674
+ repoPath: z.string(),
6675
+ branches: z.array(z.string()).min(1),
6676
+ agent: z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
6677
+ base: z.string().optional(),
6678
+ title: z.string().optional()
6679
+ },
6680
+ async (args) => {
6681
+ const result = await orch.createPrStack({
6682
+ repoPath: args.repoPath,
6683
+ branches: args.branches,
6684
+ agent: args.agent,
6685
+ base: args.base,
6686
+ title: args.title
6687
+ });
6688
+ return {
6689
+ content: [
6690
+ {
6691
+ type: "text",
6692
+ text: JSON.stringify(
6693
+ {
6694
+ stackNumber: result.stack.stackNumber,
6695
+ trunk: result.stack.trunk,
6696
+ threads: result.threads.map((t) => ({
6697
+ id: t.id,
6698
+ title: t.title,
6699
+ branchName: t.branchName,
6700
+ stackLayer: t.stackLayer,
6701
+ worktreePath: t.worktreePath,
6702
+ link: `sideboard://thread/${t.id}`
6703
+ }))
6704
+ },
6705
+ null,
6706
+ 2
6707
+ )
6708
+ }
6709
+ ]
6710
+ };
6711
+ }
6712
+ );
6033
6713
  server.tool(
6034
6714
  "list_issues",
6035
6715
  "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.",
@@ -6383,6 +7063,7 @@ async function runCloudConnect(opts) {
6383
7063
  }
6384
7064
  }
6385
7065
  export {
7066
+ ATTACHMENTS_DIR,
6386
7067
  BRIGHTSY_MCP_ALLOWED_TOOLS,
6387
7068
  BrightsySideboardApi,
6388
7069
  CLAUDE_MODEL_CATALOG,
@@ -6398,11 +7079,16 @@ export {
6398
7079
  FAMOUS_SOCCER_TEAMS,
6399
7080
  GLOBAL_WORKSPACE_ID,
6400
7081
  HARNESS_ENV_KEYS,
7082
+ LEGACY_ATTACHMENTS_DIR,
7083
+ LEGACY_PLAN_FILE_REL,
7084
+ LEGACY_REVIEW_REQUEST_PATH,
6401
7085
  MAX_ANTHROPIC_CACHE_CONTROL_BLOCKS,
6402
7086
  ORCHESTRATOR_AGENT_KINDS,
6403
7087
  Orchestrator,
6404
7088
  PASTE_ATTACH_MIN_CHARS,
6405
7089
  PASTE_ATTACH_MIN_LINES,
7090
+ PLAN_FILE_NAME,
7091
+ PLAN_FILE_REL,
6406
7092
  PLAN_MODE_INSTRUCTION,
6407
7093
  REPO_REVIEW_NAME,
6408
7094
  REPO_REVIEW_PATH,
@@ -6412,6 +7098,8 @@ export {
6412
7098
  SIDEBOARD_FORCE_STOP,
6413
7099
  SIDEBOARD_MCP_ALLOWED_TOOLS,
6414
7100
  THINKING_EFFORTS,
7101
+ addPrStackLayer,
7102
+ addStackLayerFromThread,
6415
7103
  addWorkspace,
6416
7104
  adoptThread,
6417
7105
  allAdapters,
@@ -6430,6 +7118,7 @@ export {
6430
7118
  attachmentFromAbsolutePath,
6431
7119
  attachmentsFromBuffers,
6432
7120
  attachmentsFromWorktreePaths,
7121
+ attachmentsGitignoreBody,
6433
7122
  autoCleanupOrphansEnabled,
6434
7123
  autoRenameBranchEnabled,
6435
7124
  autoRunAfterSetupEnabled,
@@ -6452,6 +7141,7 @@ export {
6452
7141
  caffeinateWhileRunningEnabled,
6453
7142
  captureLoginEnv,
6454
7143
  captureTurnBaseline,
7144
+ checkoutPrStackLayer,
6455
7145
  childEnvWithAppSettings,
6456
7146
  claudeAdapter,
6457
7147
  claudeChromeEnabled,
@@ -6471,8 +7161,10 @@ export {
6471
7161
  countCacheControlBlocks,
6472
7162
  createChatTab,
6473
7163
  createEmptyThread,
7164
+ createExistingBranchWorktree,
6474
7165
  createGlobalChat,
6475
7166
  createOrUpdatePr,
7167
+ createPrStack,
6476
7168
  createThread,
6477
7169
  createThreadWorktree,
6478
7170
  currentBranch,
@@ -6482,6 +7174,7 @@ export {
6482
7174
  deleteBranchOnPurgeEnabled,
6483
7175
  deleteThreadRecord,
6484
7176
  detectAgents,
7177
+ detectGhStack,
6485
7178
  detectLocalMergeConflicts,
6486
7179
  disconnectBrightsyTeam,
6487
7180
  discoverSkills,
@@ -6497,12 +7190,15 @@ export {
6497
7190
  estimateThreadChars,
6498
7191
  expandComposerPrompt,
6499
7192
  extractGhErrorDetail,
7193
+ extractPendingPlanQuestions,
7194
+ extractPresentedPlan,
6500
7195
  extractiveSummary,
6501
7196
  fetchPrHead,
6502
7197
  finalizeParts,
6503
7198
  findInvalidCacheControlTtlOrder,
6504
7199
  findOrphanWorktrees,
6505
7200
  findThreadByRef,
7201
+ findThreadForStackLayer,
6506
7202
  flattenTurnInput,
6507
7203
  forkChatTab,
6508
7204
  forkMessageSlice,
@@ -6513,6 +7209,8 @@ export {
6513
7209
  formatGhLandError,
6514
7210
  formatIpcInvokeError,
6515
7211
  formatMessagesAsTranscript,
7212
+ formatPlanQuestionAnswers,
7213
+ formatPlanQuestionsForChat,
6516
7214
  formatRateLimitResetHint,
6517
7215
  formatRenameBranchDirective,
6518
7216
  formatTranscriptMarkdown,
@@ -6536,6 +7234,7 @@ export {
6536
7234
  getPrChecks,
6537
7235
  getPrDetails,
6538
7236
  getPrMeta,
7237
+ getPrStack,
6539
7238
  getRepoSetupInfo,
6540
7239
  getRunMode,
6541
7240
  getRunScript,
@@ -6552,9 +7251,12 @@ export {
6552
7251
  healOrchestrationSoccerTitles,
6553
7252
  importConductorWorkspace,
6554
7253
  importConductorWorkspaceAsync,
7254
+ initPrStack,
7255
+ initStackFromThread,
6555
7256
  initializeGitRepository,
6556
7257
  inspectGitWorktree,
6557
7258
  installAgent,
7259
+ isAskUserToolName,
6558
7260
  isBrightsyConnected,
6559
7261
  isBrightsyNdjsonLine,
6560
7262
  isCloudCoordinatorThread,
@@ -6564,13 +7266,17 @@ export {
6564
7266
  isGlobalRepoPath,
6565
7267
  isGlobalThread,
6566
7268
  isImageFilePath,
7269
+ isInPrStack,
6567
7270
  isLinearConnected,
6568
7271
  isOrchestratorCapableAgent,
6569
7272
  isOrchestratorThread,
6570
7273
  isPidAlive,
6571
7274
  isPlaceholderBranch,
7275
+ isPresentPlanToolName,
6572
7276
  isSessionQuotaLimit,
7277
+ isSideboardScratchPath,
6573
7278
  isThinkingEffort,
7279
+ isWorkspaceScratchPath,
6574
7280
  listAgentSetupInfo,
6575
7281
  listBranchCommits,
6576
7282
  listBranches,
@@ -6607,6 +7313,7 @@ export {
6607
7313
  mcpAllowTools,
6608
7314
  mcpAuthWarnings,
6609
7315
  mergePr,
7316
+ mergePrStack,
6610
7317
  mergeUsage,
6611
7318
  nextPastedTextName,
6612
7319
  nextThinkingEffort,
@@ -6616,6 +7323,8 @@ export {
6616
7323
  normalizeTurnInput,
6617
7324
  normalizeWorktreePath,
6618
7325
  openInSystemTerminal,
7326
+ openPrStackLayers,
7327
+ openStackLayer,
6619
7328
  opencodeAdapter,
6620
7329
  orchestrationQuotaFallbackAgent,
6621
7330
  orchestrationQuotaOnLimit,
@@ -6624,15 +7333,19 @@ export {
6624
7333
  originGhRepoEnv,
6625
7334
  parseCursorRunnerLine,
6626
7335
  parseForceStopMessage,
7336
+ parseGhStackViewJson,
6627
7337
  parseGithubSlugFromRemoteUrl,
6628
7338
  parseMcpList,
7339
+ parsePlanQuestionsInput,
6629
7340
  parseSessionQuotaResetAt,
6630
7341
  partsToAssistantText,
6631
7342
  pastedTextStats,
6632
7343
  permissionMode,
7344
+ planFileAbs,
6633
7345
  previewLand,
6634
7346
  pushBranch,
6635
7347
  readExistingReviewRequestFile,
7348
+ readPlanFile,
6636
7349
  readSkillBody,
6637
7350
  readThread,
6638
7351
  readWorktreeFile,
@@ -6644,6 +7357,7 @@ export {
6644
7357
  repoSlug,
6645
7358
  requestReview,
6646
7359
  requireAgent,
7360
+ resetGhStackDetectCache,
6647
7361
  resolveClaudeExecutable,
6648
7362
  resolveConductorCursorAgentId,
6649
7363
  resolveCursorModelId,
@@ -6651,7 +7365,9 @@ export {
6651
7365
  resolveDiffBaseRef,
6652
7366
  resolveEffectiveIssueSource,
6653
7367
  resolveFilesToCopy,
7368
+ resolveGhAuthToken,
6654
7369
  resolveGithubRepoSlug,
7370
+ resolvePlanMarkdown,
6655
7371
  resolvePrSelector,
6656
7372
  resolveQuotaFallbackAgent,
6657
7373
  resolveRepoRoot,
@@ -6679,12 +7395,16 @@ export {
6679
7395
  slugify,
6680
7396
  spawnAgentTurn,
6681
7397
  splitForCompaction,
7398
+ stackAgentDefaultsFrom,
7399
+ stackIdFrom,
7400
+ stackMergeReadiness,
6682
7401
  stageAbsolutePathsAsAttachments,
6683
7402
  stageBuffersAsAttachments,
6684
7403
  startDevServer,
6685
7404
  startMcpServer,
6686
7405
  startOrchestration,
6687
7406
  stripBrightsyNdjsonNoise,
7407
+ submitPrStack,
6688
7408
  suggestSlug,
6689
7409
  summarizeConversation,
6690
7410
  switchBrightsyAccount,
@@ -6720,6 +7440,7 @@ export {
6720
7440
  worktreeNameFromPath,
6721
7441
  worktreesRoot,
6722
7442
  writeInjectedMcpConfig,
7443
+ writePlanFile,
6723
7444
  writeThread,
6724
7445
  writeWorktreeFile
6725
7446
  };