@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.
- package/dist/{agents-LUB3Q773.js → agents-ON6RKKND.js} +3 -3
- package/dist/{agents-3P4N6KHC.js → agents-YKSS6VBO.js} +3 -3
- package/dist/{chunk-RQI4TOXK.js → chunk-6QTZVJ7A.js} +7 -5
- package/dist/{chunk-AJ6ROGD7.js → chunk-7LNGIP3X.js} +20 -2
- package/dist/chunk-B3SJXYIJ.js +24 -0
- package/dist/{chunk-ILQK4P5R.js → chunk-BXJ76RHF.js} +4 -4
- package/dist/{chunk-SS34TVSY.js → chunk-D3METLRW.js} +7 -5
- package/dist/{chunk-FV6FN6V5.js → chunk-DZFH2KLT.js} +340 -22
- package/dist/chunk-FKOIHGKV.js +21 -0
- package/dist/{chunk-3NAS4MWH.js → chunk-GNML24AW.js} +2 -2
- package/dist/{chunk-E35APBHV.js → chunk-H6GGDLYS.js} +1 -1
- package/dist/{chunk-EOYDCKQC.js → chunk-HLEX5AQ6.js} +4 -0
- package/dist/{chunk-ZQCQIWIP.js → chunk-LRLKJM3O.js} +2 -1
- package/dist/chunk-N5PM7HGQ.js +103 -0
- package/dist/chunk-QTUESPAW.js +101 -0
- package/dist/{chunk-O6W3P7V3.js → chunk-TSRXOSVD.js} +4 -0
- package/dist/{chunk-GZXBYHJC.js → chunk-UEAHMGHW.js} +2 -1
- package/dist/{chunk-ZVV5EEQN.js → chunk-XOU6HNQJ.js} +305 -22
- package/dist/{chunk-5WYEJ3F3.js → chunk-XX5BB7NV.js} +3 -3
- package/dist/{chunk-V4JRXYYU.js → chunk-YDXQ72MD.js} +2 -2
- package/dist/{chunk-DF5VQQKA.js → chunk-YOWIYAVA.js} +3 -3
- package/dist/{chunk-ISY4EGF6.js → chunk-ZNM4MAN4.js} +20 -2
- package/dist/{connected-teams-UBXHZGO4.js → connected-teams-2ANNZCP5.js} +2 -2
- package/dist/{connected-teams-GF52Q7LB.js → connected-teams-ONC4666A.js} +2 -2
- package/dist/{coordinator-prompt-VG4BZ5JL.js → coordinator-prompt-6FXVTSFN.js} +5 -4
- package/dist/{coordinator-prompt-SPGDT7J5.js → coordinator-prompt-S6JZD5EF.js} +5 -4
- package/dist/{global-workspace-OJF4ENH4.js → global-workspace-EV4G2WMQ.js} +6 -5
- package/dist/{global-workspace-KSR3E63K.js → global-workspace-MSX2K27Y.js} +6 -5
- package/dist/index.cjs +1369 -155
- package/dist/index.d.cts +397 -14
- package/dist/index.d.ts +397 -14
- package/dist/index.js +811 -90
- package/dist/mcp/run-stdio.cjs +1136 -147
- package/dist/mcp/run-stdio.js +635 -79
- package/dist/plan-file-6O7G4VPQ.js +23 -0
- package/dist/plan-file-PHVKUAEE.js +25 -0
- package/dist/{run-HMRSRG3U.js → run-H3IMNOYN.js} +3 -1
- package/dist/run-ZJKYHXGN.js +12 -0
- package/dist/{thread-store-XICUWFNM.js → thread-store-GHOADGL2.js} +1 -1
- package/dist/{thread-store-OV2X6PYO.js → thread-store-UJIGMI5J.js} +1 -1
- package/dist/{workspaces-OZE7LRDO.js → workspaces-3RQQZQRO.js} +7 -6
- package/dist/{workspaces-UJX7JIGH.js → workspaces-AYTBR6KQ.js} +7 -6
- package/dist/{worktree-XG5PCLZY.js → worktree-5KEQWSAF.js} +8 -3
- package/dist/{worktree-TEDAJ57S.js → worktree-RWGL7FUV.js} +8 -3
- package/package.json +1 -1
- 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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
692
|
+
const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-MSX2K27Y.js");
|
|
659
693
|
if (isGlobalThread2(thread)) {
|
|
660
|
-
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-
|
|
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,
|
|
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-
|
|
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-
|
|
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,
|
|
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-
|
|
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
|
|
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 || !
|
|
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 (!
|
|
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 (!
|
|
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-
|
|
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-
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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
|
|
3685
|
+
import { existsSync as existsSync13 } from "fs";
|
|
3377
3686
|
|
|
3378
3687
|
// src/git/orphan-cleanup.ts
|
|
3379
|
-
import { existsSync as
|
|
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 (
|
|
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 || !
|
|
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 (
|
|
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 (!
|
|
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
|
|
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 (
|
|
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
|
|
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 =
|
|
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 (!
|
|
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,
|
|
3764
|
-
if (
|
|
4069
|
+
const gitignoreAbs = join10(worktreePath, ATTACHMENTS_DIR, ".gitignore");
|
|
4070
|
+
if (existsSync12(gitignoreAbs)) return;
|
|
3765
4071
|
mkdirSync4(dirname3(gitignoreAbs), { recursive: true });
|
|
3766
|
-
writeFileSync3(gitignoreAbs,
|
|
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
|
|
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 (!
|
|
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
|
|
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
|
|
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 `.
|
|
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 (!
|
|
5238
|
-
const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-
|
|
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-
|
|
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-
|
|
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
|
};
|