@sideboard-ai/core 0.1.51 → 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-HIEJL3UV.js → agents-ON6RKKND.js} +1 -1
- package/dist/{agents-5ROTZNCX.js → agents-YKSS6VBO.js} +1 -1
- package/dist/{chunk-7EUWSBWR.js → chunk-6QTZVJ7A.js} +5 -3
- package/dist/chunk-B3SJXYIJ.js +24 -0
- package/dist/{chunk-5ZPSH7VI.js → chunk-D3METLRW.js} +5 -3
- package/dist/{chunk-ZH5QZ4CR.js → chunk-DZFH2KLT.js} +280 -7
- package/dist/chunk-FKOIHGKV.js +21 -0
- package/dist/{chunk-E4VVEKAM.js → chunk-GNML24AW.js} +2 -2
- package/dist/{chunk-EOYDCKQC.js → chunk-HLEX5AQ6.js} +4 -0
- package/dist/{chunk-K3WMKGFY.js → chunk-LRLKJM3O.js} +2 -1
- package/dist/chunk-N5PM7HGQ.js +103 -0
- package/dist/chunk-QTUESPAW.js +101 -0
- package/dist/{chunk-O6W3P7V3.js → chunk-TSRXOSVD.js} +4 -0
- package/dist/{chunk-F4Q3IM6V.js → chunk-UEAHMGHW.js} +2 -1
- package/dist/{chunk-XRSAGVRW.js → chunk-XOU6HNQJ.js} +245 -7
- package/dist/{chunk-O5DOO7DP.js → chunk-XX5BB7NV.js} +3 -3
- package/dist/{chunk-QN7XNQAT.js → chunk-YDXQ72MD.js} +2 -2
- package/dist/{chunk-YFJ4FG2P.js → chunk-YOWIYAVA.js} +3 -3
- package/dist/{coordinator-prompt-7HHJRO7B.js → coordinator-prompt-6FXVTSFN.js} +4 -3
- package/dist/{coordinator-prompt-WD7FAMA2.js → coordinator-prompt-S6JZD5EF.js} +4 -3
- package/dist/{global-workspace-OJEPGDXA.js → global-workspace-EV4G2WMQ.js} +5 -4
- package/dist/{global-workspace-ECYN2MKL.js → global-workspace-MSX2K27Y.js} +5 -4
- package/dist/index.cjs +1288 -140
- package/dist/index.d.cts +388 -15
- package/dist/index.d.ts +388 -15
- package/dist/index.js +801 -84
- package/dist/mcp/run-stdio.cjs +1059 -132
- package/dist/mcp/run-stdio.js +628 -73
- package/dist/plan-file-6O7G4VPQ.js +23 -0
- package/dist/plan-file-PHVKUAEE.js +25 -0
- package/dist/{thread-store-XICUWFNM.js → thread-store-GHOADGL2.js} +1 -1
- package/dist/{thread-store-OV2X6PYO.js → thread-store-UJIGMI5J.js} +1 -1
- package/dist/{workspaces-MUU7RGVV.js → workspaces-3RQQZQRO.js} +6 -5
- package/dist/{workspaces-ZWOOFZUV.js → workspaces-AYTBR6KQ.js} +6 -5
- package/dist/{worktree-DVNDMWZ7.js → worktree-5KEQWSAF.js} +5 -2
- package/dist/{worktree-GDV56MX4.js → worktree-RWGL7FUV.js} +5 -2
- package/package.json +1 -1
package/dist/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,
|
|
@@ -89,6 +89,17 @@ import {
|
|
|
89
89
|
saveBrightsyConfig,
|
|
90
90
|
switchBrightsyAccount
|
|
91
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,10 +181,13 @@ 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,
|
|
174
192
|
isSideboardScratchPath,
|
|
175
193
|
listBranches,
|
|
@@ -177,11 +195,14 @@ import {
|
|
|
177
195
|
listWorktrees,
|
|
178
196
|
lookupSoccerTeam,
|
|
179
197
|
mergePr,
|
|
198
|
+
mergePrStack,
|
|
180
199
|
normalizeWorktreePath,
|
|
181
200
|
originGhRepoEnv,
|
|
201
|
+
parseGhStackViewJson,
|
|
182
202
|
parseGithubSlugFromRemoteUrl,
|
|
183
203
|
pushBranch,
|
|
184
204
|
removeWorktree,
|
|
205
|
+
resetGhStackDetectCache,
|
|
185
206
|
resolveDefaultBranch,
|
|
186
207
|
resolveDiffBaseRef,
|
|
187
208
|
resolveGithubRepoSlug,
|
|
@@ -189,13 +210,21 @@ import {
|
|
|
189
210
|
resolveRepoRoot,
|
|
190
211
|
resolveWorktreeStartPoint,
|
|
191
212
|
slugify,
|
|
213
|
+
stackMergeReadiness,
|
|
214
|
+
submitPrStack,
|
|
192
215
|
suggestSlug,
|
|
193
216
|
takenSlugsFromThread,
|
|
194
217
|
threadDisplayLabel,
|
|
195
218
|
worktreeDisplayLabel,
|
|
196
219
|
worktreeDisplayLabelForGroup,
|
|
197
220
|
worktreeNameFromPath
|
|
198
|
-
} 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";
|
|
199
228
|
import {
|
|
200
229
|
appendMessage,
|
|
201
230
|
createEmptyThread,
|
|
@@ -209,7 +238,7 @@ import {
|
|
|
209
238
|
updateThread,
|
|
210
239
|
withThreadLock,
|
|
211
240
|
writeThread
|
|
212
|
-
} from "./chunk-
|
|
241
|
+
} from "./chunk-TSRXOSVD.js";
|
|
213
242
|
import {
|
|
214
243
|
THINKING_EFFORTS,
|
|
215
244
|
isThinkingEffort,
|
|
@@ -458,6 +487,9 @@ function toolDescription(name, input) {
|
|
|
458
487
|
if (/present_artifact$/i.test(name)) {
|
|
459
488
|
return str(input?.title) ? `Present ${str(input?.title)}` : "Present artifact";
|
|
460
489
|
}
|
|
490
|
+
if (/present_plan$/i.test(name)) {
|
|
491
|
+
return str(input?.title) ? `Plan ${str(input?.title)}` : "Present plan";
|
|
492
|
+
}
|
|
461
493
|
if (/present_schema$/i.test(name)) {
|
|
462
494
|
return str(input?.title) ? `Schema ${str(input?.title)}` : "Present schema";
|
|
463
495
|
}
|
|
@@ -657,9 +689,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
657
689
|
`Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
|
|
658
690
|
);
|
|
659
691
|
}
|
|
660
|
-
const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-
|
|
692
|
+
const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-MSX2K27Y.js");
|
|
661
693
|
if (isGlobalThread2(thread)) {
|
|
662
|
-
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-
|
|
694
|
+
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-S6JZD5EF.js");
|
|
663
695
|
ensureGlobalCoordinatorCwd2();
|
|
664
696
|
}
|
|
665
697
|
if (isOrchestratorThread(thread)) {
|
|
@@ -2181,11 +2213,6 @@ var IMAGE_MIME_BY_EXT = {
|
|
|
2181
2213
|
bmp: "image/bmp",
|
|
2182
2214
|
ico: "image/x-icon"
|
|
2183
2215
|
};
|
|
2184
|
-
var ATTACHMENTS_DIR = ".sideboard/attachments";
|
|
2185
|
-
var ATTACHMENTS_GITIGNORE = `# Sideboard review / composer attachments (local only)
|
|
2186
|
-
*
|
|
2187
|
-
!.gitignore
|
|
2188
|
-
`;
|
|
2189
2216
|
var MAX_INLINE_BYTES = 4e5;
|
|
2190
2217
|
var MAX_PREVIEW_BYTES = 5e6;
|
|
2191
2218
|
function fileExtension(filePath) {
|
|
@@ -2203,7 +2230,7 @@ function ensureAttachmentsDir(worktreePath) {
|
|
|
2203
2230
|
mkdirSync3(dir, { recursive: true });
|
|
2204
2231
|
const gi = join6(dir, ".gitignore");
|
|
2205
2232
|
if (!existsSync6(gi)) {
|
|
2206
|
-
writeFileSync2(gi,
|
|
2233
|
+
writeFileSync2(gi, attachmentsGitignoreBody(), "utf8");
|
|
2207
2234
|
}
|
|
2208
2235
|
return dir;
|
|
2209
2236
|
}
|
|
@@ -2853,7 +2880,7 @@ async function confirmLand(thread, opts) {
|
|
|
2853
2880
|
|
|
2854
2881
|
// src/threads/create.ts
|
|
2855
2882
|
import { existsSync as existsSync7 } from "fs";
|
|
2856
|
-
async function createThread(input,
|
|
2883
|
+
async function createThread(input, _onSetupLine) {
|
|
2857
2884
|
await requireAgent(input.agent);
|
|
2858
2885
|
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
2859
2886
|
if (!existsSync7(repoPath)) {
|
|
@@ -2911,26 +2938,10 @@ async function createThread(input, onSetupLine) {
|
|
|
2911
2938
|
});
|
|
2912
2939
|
writeThread(thread);
|
|
2913
2940
|
await ensureWorkspace(repoPath);
|
|
2914
|
-
try {
|
|
2915
|
-
let setup = await runSetupScript(repoPath, worktreePath, onSetupLine);
|
|
2916
|
-
if (!setup.ran) {
|
|
2917
|
-
setup = await runCursorWorktreeSetup(repoPath, worktreePath, onSetupLine);
|
|
2918
|
-
}
|
|
2919
|
-
if (setup.ran && setup.exitCode !== 0 && setup.exitCode !== null) {
|
|
2920
|
-
updateThread(thread.id, {
|
|
2921
|
-
lastError: `Setup exited ${setup.exitCode} (thread is still usable)`
|
|
2922
|
-
});
|
|
2923
|
-
}
|
|
2924
|
-
} catch (err) {
|
|
2925
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2926
|
-
updateThread(thread.id, {
|
|
2927
|
-
lastError: `Setup failed: ${message}`
|
|
2928
|
-
});
|
|
2929
|
-
}
|
|
2930
2941
|
return readThread(thread.id) ?? thread;
|
|
2931
2942
|
}
|
|
2932
2943
|
async function listLinearIssues(agent, repoPath) {
|
|
2933
|
-
const { getAdapter: getAdapter2 } = await import("./agents-
|
|
2944
|
+
const { getAdapter: getAdapter2 } = await import("./agents-YKSS6VBO.js");
|
|
2934
2945
|
await requireAgent(agent, { requireLinear: true });
|
|
2935
2946
|
const adapter = getAdapter2(agent);
|
|
2936
2947
|
if (!adapter.listLinearIssues) {
|
|
@@ -2973,7 +2984,9 @@ function worktreeBindingFrom(from) {
|
|
|
2973
2984
|
sourceIsFork: from.sourceIsFork,
|
|
2974
2985
|
parentThreadId: from.parentThreadId,
|
|
2975
2986
|
prUrl: from.prUrl,
|
|
2976
|
-
prTitle: from.prTitle
|
|
2987
|
+
prTitle: from.prTitle,
|
|
2988
|
+
stackId: from.stackId,
|
|
2989
|
+
stackLayer: from.stackLayer
|
|
2977
2990
|
};
|
|
2978
2991
|
}
|
|
2979
2992
|
function threadsSharingWorktree(worktreePath) {
|
|
@@ -3086,11 +3099,305 @@ async function forkThreadWorktree(input, onSetupLine) {
|
|
|
3086
3099
|
return thread;
|
|
3087
3100
|
}
|
|
3088
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
|
+
|
|
3089
3396
|
// src/threads/adopt.ts
|
|
3090
3397
|
import { execFileSync } from "child_process";
|
|
3091
3398
|
import {
|
|
3092
3399
|
copyFileSync as copyFileSync3,
|
|
3093
|
-
existsSync as
|
|
3400
|
+
existsSync as existsSync9,
|
|
3094
3401
|
mkdtempSync,
|
|
3095
3402
|
readdirSync as readdirSync3,
|
|
3096
3403
|
readFileSync as readFileSync7,
|
|
@@ -3118,7 +3425,7 @@ function mapAgentType(raw) {
|
|
|
3118
3425
|
return null;
|
|
3119
3426
|
}
|
|
3120
3427
|
function resolveConductorCursorAgentId(workspacePath) {
|
|
3121
|
-
if (!workspacePath || !
|
|
3428
|
+
if (!workspacePath || !existsSync9(CURSOR_SDK_STORE)) return null;
|
|
3122
3429
|
const normalized = workspacePath.replace(/\/$/, "");
|
|
3123
3430
|
let best = null;
|
|
3124
3431
|
let hashes;
|
|
@@ -3129,7 +3436,7 @@ function resolveConductorCursorAgentId(workspacePath) {
|
|
|
3129
3436
|
}
|
|
3130
3437
|
for (const hash of hashes) {
|
|
3131
3438
|
const agentsFile = join7(CURSOR_SDK_STORE, hash, "agents.ndjson");
|
|
3132
|
-
if (!
|
|
3439
|
+
if (!existsSync9(agentsFile)) continue;
|
|
3133
3440
|
let text;
|
|
3134
3441
|
try {
|
|
3135
3442
|
text = readFileSync7(agentsFile, "utf8");
|
|
@@ -3156,7 +3463,7 @@ function resolveConductorCursorAgentId(workspacePath) {
|
|
|
3156
3463
|
return best?.agentId ?? null;
|
|
3157
3464
|
}
|
|
3158
3465
|
async function adoptThread(input) {
|
|
3159
|
-
if (!
|
|
3466
|
+
if (!existsSync9(input.worktreePath)) {
|
|
3160
3467
|
throw new Error(`Worktree not found: ${input.worktreePath}`);
|
|
3161
3468
|
}
|
|
3162
3469
|
const repoPath = await resolveRepoRoot(input.worktreePath);
|
|
@@ -3175,7 +3482,7 @@ async function adoptThread(input) {
|
|
|
3175
3482
|
messages: input.messages ?? []
|
|
3176
3483
|
});
|
|
3177
3484
|
writeThread(thread);
|
|
3178
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-
|
|
3485
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-AYTBR6KQ.js");
|
|
3179
3486
|
await ensureWorkspace2(repoPath);
|
|
3180
3487
|
return thread;
|
|
3181
3488
|
}
|
|
@@ -3183,7 +3490,7 @@ function conductorDbPath() {
|
|
|
3183
3490
|
return CONDUCTOR_DB;
|
|
3184
3491
|
}
|
|
3185
3492
|
function listConductorWorkspaces() {
|
|
3186
|
-
if (!
|
|
3493
|
+
if (!existsSync9(CONDUCTOR_DB)) {
|
|
3187
3494
|
throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
|
|
3188
3495
|
}
|
|
3189
3496
|
const tmp = mkdtempSync(join7(tmpdir(), "sideboard-conductor-"));
|
|
@@ -3192,7 +3499,7 @@ function listConductorWorkspaces() {
|
|
|
3192
3499
|
copyFileSync3(CONDUCTOR_DB, snapshot);
|
|
3193
3500
|
for (const suffix of ["-wal", "-shm"]) {
|
|
3194
3501
|
const src = `${CONDUCTOR_DB}${suffix}`;
|
|
3195
|
-
if (
|
|
3502
|
+
if (existsSync9(src)) {
|
|
3196
3503
|
try {
|
|
3197
3504
|
copyFileSync3(src, `${snapshot}${suffix}`);
|
|
3198
3505
|
} catch {
|
|
@@ -3274,7 +3581,7 @@ function listConductorWorkspaces() {
|
|
|
3274
3581
|
}
|
|
3275
3582
|
}
|
|
3276
3583
|
function importConductorWorkspace(workspaceId) {
|
|
3277
|
-
if (!
|
|
3584
|
+
if (!existsSync9(CONDUCTOR_DB)) {
|
|
3278
3585
|
throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
|
|
3279
3586
|
}
|
|
3280
3587
|
const tmp = mkdtempSync(join7(tmpdir(), "sideboard-conductor-"));
|
|
@@ -3283,7 +3590,7 @@ function importConductorWorkspace(workspaceId) {
|
|
|
3283
3590
|
copyFileSync3(CONDUCTOR_DB, snapshot);
|
|
3284
3591
|
for (const suffix of ["-wal", "-shm"]) {
|
|
3285
3592
|
const src = `${CONDUCTOR_DB}${suffix}`;
|
|
3286
|
-
if (
|
|
3593
|
+
if (existsSync9(src)) {
|
|
3287
3594
|
try {
|
|
3288
3595
|
copyFileSync3(src, `${snapshot}${suffix}`);
|
|
3289
3596
|
} catch {
|
|
@@ -3303,7 +3610,7 @@ function importConductorWorkspace(workspaceId) {
|
|
|
3303
3610
|
).get(workspaceId);
|
|
3304
3611
|
if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
|
|
3305
3612
|
const worktreePath = String(row.workspacePath);
|
|
3306
|
-
if (!
|
|
3613
|
+
if (!existsSync9(worktreePath)) {
|
|
3307
3614
|
throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
|
|
3308
3615
|
}
|
|
3309
3616
|
let sessionId = null;
|
|
@@ -3375,10 +3682,10 @@ async function importConductorWorkspaceAsync(workspaceId) {
|
|
|
3375
3682
|
|
|
3376
3683
|
// src/orchestrator/orchestrator.ts
|
|
3377
3684
|
import { EventEmitter } from "events";
|
|
3378
|
-
import { existsSync as
|
|
3685
|
+
import { existsSync as existsSync13 } from "fs";
|
|
3379
3686
|
|
|
3380
3687
|
// src/git/orphan-cleanup.ts
|
|
3381
|
-
import { existsSync as
|
|
3688
|
+
import { existsSync as existsSync10, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
|
|
3382
3689
|
import { join as join8 } from "path";
|
|
3383
3690
|
function isSideboardWorktreePath(path) {
|
|
3384
3691
|
return path.includes("/.sideboard/worktrees/") || path.includes("/sideboard/workspaces/");
|
|
@@ -3390,7 +3697,7 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
3390
3697
|
repoPaths?.length ? repoPaths : threads.map((t) => t.repoPath).filter(Boolean)
|
|
3391
3698
|
);
|
|
3392
3699
|
const homeRoot = sideboardWorkspacesDir();
|
|
3393
|
-
if (
|
|
3700
|
+
if (existsSync10(homeRoot)) {
|
|
3394
3701
|
try {
|
|
3395
3702
|
for (const entry of readdirSync4(homeRoot, { withFileTypes: true })) {
|
|
3396
3703
|
if (!entry.isDirectory()) continue;
|
|
@@ -3402,7 +3709,7 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
3402
3709
|
const orphans = [];
|
|
3403
3710
|
const seen = /* @__PURE__ */ new Set();
|
|
3404
3711
|
for (const repoPath of repos) {
|
|
3405
|
-
if (!repoPath || !
|
|
3712
|
+
if (!repoPath || !existsSync10(repoPath)) continue;
|
|
3406
3713
|
try {
|
|
3407
3714
|
const wts = await listWorktrees(repoPath);
|
|
3408
3715
|
for (const wt of wts) {
|
|
@@ -3423,12 +3730,12 @@ async function findOrphanWorktrees(repoPaths) {
|
|
|
3423
3730
|
}
|
|
3424
3731
|
try {
|
|
3425
3732
|
const root = worktreesRoot(repoPath);
|
|
3426
|
-
if (
|
|
3733
|
+
if (existsSync10(root)) {
|
|
3427
3734
|
for (const entry of readdirSync4(root, { withFileTypes: true })) {
|
|
3428
3735
|
if (!entry.isDirectory()) continue;
|
|
3429
3736
|
const path = join8(root, entry.name).replace(/\/$/, "");
|
|
3430
3737
|
if (known.has(path) || seen.has(path)) continue;
|
|
3431
|
-
if (!
|
|
3738
|
+
if (!existsSync10(join8(path, ".git"))) continue;
|
|
3432
3739
|
seen.add(path);
|
|
3433
3740
|
let mtimeMs = 0;
|
|
3434
3741
|
try {
|
|
@@ -3577,7 +3884,7 @@ async function applyThreadIntoMain(thread, opts) {
|
|
|
3577
3884
|
}
|
|
3578
3885
|
|
|
3579
3886
|
// src/git/clone-repo.ts
|
|
3580
|
-
import { existsSync as
|
|
3887
|
+
import { existsSync as existsSync11 } from "fs";
|
|
3581
3888
|
import { basename as basename3, join as join9 } from "path";
|
|
3582
3889
|
import { execa as execa5 } from "execa";
|
|
3583
3890
|
async function cloneRepoIntoSideboard(opts) {
|
|
@@ -3590,7 +3897,7 @@ async function cloneRepoIntoSideboard(opts) {
|
|
|
3590
3897
|
}
|
|
3591
3898
|
name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
|
|
3592
3899
|
const dest = join9(sideboardReposDir(), name);
|
|
3593
|
-
if (
|
|
3900
|
+
if (existsSync11(dest)) {
|
|
3594
3901
|
const repoPath2 = await resolveRepoRoot(dest);
|
|
3595
3902
|
const workspace2 = await ensureWorkspace(repoPath2);
|
|
3596
3903
|
return { repoPath: repoPath2, workspace: workspace2 };
|
|
@@ -3608,7 +3915,7 @@ async function cloneRepoIntoSideboard(opts) {
|
|
|
3608
3915
|
|
|
3609
3916
|
// src/review/request-review.ts
|
|
3610
3917
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
3611
|
-
import { existsSync as
|
|
3918
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
|
|
3612
3919
|
import { dirname as dirname3, join as join10 } from "path";
|
|
3613
3920
|
|
|
3614
3921
|
// src/review/review-request-template.ts
|
|
@@ -3733,13 +4040,10 @@ File: src/client/frontends/desktop/core/UserData.ts
|
|
|
3733
4040
|
// src/review/request-review.ts
|
|
3734
4041
|
var REPO_REVIEW_PATH = ".sideboard/review.md";
|
|
3735
4042
|
var REPO_REVIEW_NAME = "review.md";
|
|
3736
|
-
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`;
|
|
3737
4045
|
var REVIEW_REQUEST_NAME = "Review request.md";
|
|
3738
4046
|
var REVIEW_REQUEST_PREFILL = "Review.";
|
|
3739
|
-
var ATTACHMENTS_GITIGNORE2 = `# Sideboard review / composer attachments (local only)
|
|
3740
|
-
*
|
|
3741
|
-
!.gitignore
|
|
3742
|
-
`;
|
|
3743
4047
|
var LEGACY_REVIEW_TEMPLATE_MARKERS = [
|
|
3744
4048
|
"You are acting as a reviewer for a proposed code change made by another engineer.",
|
|
3745
4049
|
"HOW MANY FINDINGS TO RETURN:"
|
|
@@ -3753,7 +4057,7 @@ function shouldRefreshReviewRequestTemplate(content) {
|
|
|
3753
4057
|
return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
|
|
3754
4058
|
}
|
|
3755
4059
|
function readTextIfPresent(abs) {
|
|
3756
|
-
if (!
|
|
4060
|
+
if (!existsSync12(abs)) return null;
|
|
3757
4061
|
try {
|
|
3758
4062
|
const content = readFileSync8(abs, "utf8");
|
|
3759
4063
|
return content.trim() ? content : null;
|
|
@@ -3762,10 +4066,10 @@ function readTextIfPresent(abs) {
|
|
|
3762
4066
|
}
|
|
3763
4067
|
}
|
|
3764
4068
|
function ensureAttachmentsGitignore(worktreePath) {
|
|
3765
|
-
const gitignoreAbs = join10(worktreePath,
|
|
3766
|
-
if (
|
|
4069
|
+
const gitignoreAbs = join10(worktreePath, ATTACHMENTS_DIR, ".gitignore");
|
|
4070
|
+
if (existsSync12(gitignoreAbs)) return;
|
|
3767
4071
|
mkdirSync4(dirname3(gitignoreAbs), { recursive: true });
|
|
3768
|
-
writeFileSync3(gitignoreAbs,
|
|
4072
|
+
writeFileSync3(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
|
|
3769
4073
|
}
|
|
3770
4074
|
function resolveReviewGuidelines(worktreePath) {
|
|
3771
4075
|
const repoAbs = join10(worktreePath, REPO_REVIEW_PATH);
|
|
@@ -3788,6 +4092,16 @@ function resolveReviewGuidelines(worktreePath) {
|
|
|
3788
4092
|
source: "local"
|
|
3789
4093
|
};
|
|
3790
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
|
+
}
|
|
3791
4105
|
ensureAttachmentsGitignore(worktreePath);
|
|
3792
4106
|
mkdirSync4(dirname3(localAbs), { recursive: true });
|
|
3793
4107
|
writeFileSync3(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
|
|
@@ -3810,10 +4124,11 @@ function ensureReviewRequestFile(worktreePath) {
|
|
|
3810
4124
|
};
|
|
3811
4125
|
}
|
|
3812
4126
|
const localAbs = join10(worktreePath, REVIEW_REQUEST_PATH);
|
|
3813
|
-
const localContent = readTextIfPresent(localAbs);
|
|
4127
|
+
const localContent = readTextIfPresent(localAbs) ?? readTextIfPresent(join10(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
|
|
3814
4128
|
if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
|
|
4129
|
+
const path = existsSync12(localAbs) ? REVIEW_REQUEST_PATH : LEGACY_REVIEW_REQUEST_PATH;
|
|
3815
4130
|
return {
|
|
3816
|
-
path
|
|
4131
|
+
path,
|
|
3817
4132
|
name: REVIEW_REQUEST_NAME,
|
|
3818
4133
|
content: localContent,
|
|
3819
4134
|
source: "local"
|
|
@@ -3840,7 +4155,7 @@ function buildReviewRequestAttachment(content, opts) {
|
|
|
3840
4155
|
};
|
|
3841
4156
|
}
|
|
3842
4157
|
function readExistingReviewRequestFile(worktreePath) {
|
|
3843
|
-
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));
|
|
3844
4159
|
}
|
|
3845
4160
|
async function requestReview(threadRef, send) {
|
|
3846
4161
|
const from = findThreadByRef(threadRef);
|
|
@@ -4084,7 +4399,7 @@ var Orchestrator = class {
|
|
|
4084
4399
|
}
|
|
4085
4400
|
continue;
|
|
4086
4401
|
}
|
|
4087
|
-
if (!
|
|
4402
|
+
if (!existsSync13(thread.worktreePath)) {
|
|
4088
4403
|
setStatus(thread.id, "broken", "Worktree missing on disk");
|
|
4089
4404
|
this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
|
|
4090
4405
|
continue;
|
|
@@ -4229,14 +4544,9 @@ var Orchestrator = class {
|
|
|
4229
4544
|
return findThreadByRef(idOrRef) ?? readThread(idOrRef);
|
|
4230
4545
|
}
|
|
4231
4546
|
async createThread(input) {
|
|
4232
|
-
let thread = await createThread(input
|
|
4233
|
-
this.emit({
|
|
4234
|
-
type: "turn_output",
|
|
4235
|
-
threadId: "pending",
|
|
4236
|
-
event: { type: "stdout", data: line }
|
|
4237
|
-
});
|
|
4238
|
-
});
|
|
4547
|
+
let thread = await createThread(input);
|
|
4239
4548
|
this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
|
|
4549
|
+
await this.runSetupAfterCreate(thread.id);
|
|
4240
4550
|
const { autoRunAfterSetupEnabled: autoRunAfterSetupEnabled2 } = await import("./app-settings-LZP632KI.js");
|
|
4241
4551
|
if (autoRunAfterSetupEnabled2()) {
|
|
4242
4552
|
try {
|
|
@@ -4250,6 +4560,19 @@ var Orchestrator = class {
|
|
|
4250
4560
|
}
|
|
4251
4561
|
return thread;
|
|
4252
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
|
+
}
|
|
4253
4576
|
listWorkspaces() {
|
|
4254
4577
|
const fromThreads = listThreads({ includeArchived: false }).map((t) => t.repoPath);
|
|
4255
4578
|
return syncWorkspacesFromThreads(fromThreads);
|
|
@@ -4598,6 +4921,19 @@ var Orchestrator = class {
|
|
|
4598
4921
|
});
|
|
4599
4922
|
}
|
|
4600
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
|
+
}
|
|
4601
4937
|
if (afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
|
|
4602
4938
|
(p) => p.type === "tool" && /exitplanmode/i.test(p.name)
|
|
4603
4939
|
)) {
|
|
@@ -5079,6 +5415,82 @@ var Orchestrator = class {
|
|
|
5079
5415
|
}
|
|
5080
5416
|
return meta;
|
|
5081
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
|
+
}
|
|
5082
5494
|
async getPrDetails(threadRef) {
|
|
5083
5495
|
const { thread, selector, cwd } = await this.withPrSelector(threadRef);
|
|
5084
5496
|
if (!selector) return null;
|
|
@@ -5141,14 +5553,9 @@ var Orchestrator = class {
|
|
|
5141
5553
|
return forkChatTab(input);
|
|
5142
5554
|
}
|
|
5143
5555
|
async forkThreadWorktree(input) {
|
|
5144
|
-
const thread = await forkThreadWorktree(input
|
|
5145
|
-
this.emit({
|
|
5146
|
-
type: "turn_output",
|
|
5147
|
-
threadId: "pending",
|
|
5148
|
-
event: { type: "stdout", data: line }
|
|
5149
|
-
});
|
|
5150
|
-
});
|
|
5556
|
+
const thread = await forkThreadWorktree(input);
|
|
5151
5557
|
this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
|
|
5558
|
+
await this.runSetupAfterCreate(thread.id);
|
|
5152
5559
|
return thread;
|
|
5153
5560
|
}
|
|
5154
5561
|
renameThread(threadRef, title) {
|
|
@@ -5162,7 +5569,7 @@ var Orchestrator = class {
|
|
|
5162
5569
|
}
|
|
5163
5570
|
/**
|
|
5164
5571
|
* Stage OS / worktree files into composer attachments (copies external files
|
|
5165
|
-
* into `.
|
|
5572
|
+
* into `.context/attachments/` so agents can Read images and binaries).
|
|
5166
5573
|
*/
|
|
5167
5574
|
attachComposerFiles(threadRef, opts) {
|
|
5168
5575
|
const thread = this.requireThread(threadRef);
|
|
@@ -5236,8 +5643,8 @@ var Orchestrator = class {
|
|
|
5236
5643
|
updateThread(thread.id, { worktreePath: globalAgentCwd2() });
|
|
5237
5644
|
return setStatus(thread.id, "idle");
|
|
5238
5645
|
}
|
|
5239
|
-
if (!
|
|
5240
|
-
const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-
|
|
5646
|
+
if (!existsSync13(thread.worktreePath)) {
|
|
5647
|
+
const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-RWGL7FUV.js");
|
|
5241
5648
|
const { execa: execa6 } = await import("execa");
|
|
5242
5649
|
const slug = thread.worktreePath.split("/").pop();
|
|
5243
5650
|
const dest = thread.worktreePath;
|
|
@@ -5322,7 +5729,7 @@ async function startOrchestration(opts) {
|
|
|
5322
5729
|
sourceRef: "default",
|
|
5323
5730
|
...createOpts
|
|
5324
5731
|
}).catch(async () => {
|
|
5325
|
-
const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-
|
|
5732
|
+
const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-RWGL7FUV.js");
|
|
5326
5733
|
const repo = await resolveRepoRoot2(repoPath);
|
|
5327
5734
|
const def = await resolveDefaultBranch2(repo);
|
|
5328
5735
|
return createThread({
|
|
@@ -5332,7 +5739,7 @@ async function startOrchestration(opts) {
|
|
|
5332
5739
|
repoPath: repo
|
|
5333
5740
|
});
|
|
5334
5741
|
});
|
|
5335
|
-
const { updateThread: upd } = await import("./thread-store-
|
|
5742
|
+
const { updateThread: upd } = await import("./thread-store-UJIGMI5J.js");
|
|
5336
5743
|
const updated = upd(thread.id, {
|
|
5337
5744
|
sourceType: "orchestration",
|
|
5338
5745
|
sourceRef: goal
|
|
@@ -5343,6 +5750,104 @@ async function startOrchestration(opts) {
|
|
|
5343
5750
|
return updated;
|
|
5344
5751
|
}
|
|
5345
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
|
+
|
|
5346
5851
|
// src/mcp/server.ts
|
|
5347
5852
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5348
5853
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -5454,6 +5959,59 @@ async function startMcpServer() {
|
|
|
5454
5959
|
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
5455
5960
|
}
|
|
5456
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
|
+
);
|
|
5457
6015
|
server.tool(
|
|
5458
6016
|
"present_schema",
|
|
5459
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.",
|
|
@@ -6032,6 +6590,126 @@ async function startMcpServer() {
|
|
|
6032
6590
|
};
|
|
6033
6591
|
}
|
|
6034
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
|
+
);
|
|
6035
6713
|
server.tool(
|
|
6036
6714
|
"list_issues",
|
|
6037
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.",
|
|
@@ -6385,6 +7063,7 @@ async function runCloudConnect(opts) {
|
|
|
6385
7063
|
}
|
|
6386
7064
|
}
|
|
6387
7065
|
export {
|
|
7066
|
+
ATTACHMENTS_DIR,
|
|
6388
7067
|
BRIGHTSY_MCP_ALLOWED_TOOLS,
|
|
6389
7068
|
BrightsySideboardApi,
|
|
6390
7069
|
CLAUDE_MODEL_CATALOG,
|
|
@@ -6400,11 +7079,16 @@ export {
|
|
|
6400
7079
|
FAMOUS_SOCCER_TEAMS,
|
|
6401
7080
|
GLOBAL_WORKSPACE_ID,
|
|
6402
7081
|
HARNESS_ENV_KEYS,
|
|
7082
|
+
LEGACY_ATTACHMENTS_DIR,
|
|
7083
|
+
LEGACY_PLAN_FILE_REL,
|
|
7084
|
+
LEGACY_REVIEW_REQUEST_PATH,
|
|
6403
7085
|
MAX_ANTHROPIC_CACHE_CONTROL_BLOCKS,
|
|
6404
7086
|
ORCHESTRATOR_AGENT_KINDS,
|
|
6405
7087
|
Orchestrator,
|
|
6406
7088
|
PASTE_ATTACH_MIN_CHARS,
|
|
6407
7089
|
PASTE_ATTACH_MIN_LINES,
|
|
7090
|
+
PLAN_FILE_NAME,
|
|
7091
|
+
PLAN_FILE_REL,
|
|
6408
7092
|
PLAN_MODE_INSTRUCTION,
|
|
6409
7093
|
REPO_REVIEW_NAME,
|
|
6410
7094
|
REPO_REVIEW_PATH,
|
|
@@ -6414,6 +7098,8 @@ export {
|
|
|
6414
7098
|
SIDEBOARD_FORCE_STOP,
|
|
6415
7099
|
SIDEBOARD_MCP_ALLOWED_TOOLS,
|
|
6416
7100
|
THINKING_EFFORTS,
|
|
7101
|
+
addPrStackLayer,
|
|
7102
|
+
addStackLayerFromThread,
|
|
6417
7103
|
addWorkspace,
|
|
6418
7104
|
adoptThread,
|
|
6419
7105
|
allAdapters,
|
|
@@ -6432,6 +7118,7 @@ export {
|
|
|
6432
7118
|
attachmentFromAbsolutePath,
|
|
6433
7119
|
attachmentsFromBuffers,
|
|
6434
7120
|
attachmentsFromWorktreePaths,
|
|
7121
|
+
attachmentsGitignoreBody,
|
|
6435
7122
|
autoCleanupOrphansEnabled,
|
|
6436
7123
|
autoRenameBranchEnabled,
|
|
6437
7124
|
autoRunAfterSetupEnabled,
|
|
@@ -6454,6 +7141,7 @@ export {
|
|
|
6454
7141
|
caffeinateWhileRunningEnabled,
|
|
6455
7142
|
captureLoginEnv,
|
|
6456
7143
|
captureTurnBaseline,
|
|
7144
|
+
checkoutPrStackLayer,
|
|
6457
7145
|
childEnvWithAppSettings,
|
|
6458
7146
|
claudeAdapter,
|
|
6459
7147
|
claudeChromeEnabled,
|
|
@@ -6473,8 +7161,10 @@ export {
|
|
|
6473
7161
|
countCacheControlBlocks,
|
|
6474
7162
|
createChatTab,
|
|
6475
7163
|
createEmptyThread,
|
|
7164
|
+
createExistingBranchWorktree,
|
|
6476
7165
|
createGlobalChat,
|
|
6477
7166
|
createOrUpdatePr,
|
|
7167
|
+
createPrStack,
|
|
6478
7168
|
createThread,
|
|
6479
7169
|
createThreadWorktree,
|
|
6480
7170
|
currentBranch,
|
|
@@ -6484,6 +7174,7 @@ export {
|
|
|
6484
7174
|
deleteBranchOnPurgeEnabled,
|
|
6485
7175
|
deleteThreadRecord,
|
|
6486
7176
|
detectAgents,
|
|
7177
|
+
detectGhStack,
|
|
6487
7178
|
detectLocalMergeConflicts,
|
|
6488
7179
|
disconnectBrightsyTeam,
|
|
6489
7180
|
discoverSkills,
|
|
@@ -6499,12 +7190,15 @@ export {
|
|
|
6499
7190
|
estimateThreadChars,
|
|
6500
7191
|
expandComposerPrompt,
|
|
6501
7192
|
extractGhErrorDetail,
|
|
7193
|
+
extractPendingPlanQuestions,
|
|
7194
|
+
extractPresentedPlan,
|
|
6502
7195
|
extractiveSummary,
|
|
6503
7196
|
fetchPrHead,
|
|
6504
7197
|
finalizeParts,
|
|
6505
7198
|
findInvalidCacheControlTtlOrder,
|
|
6506
7199
|
findOrphanWorktrees,
|
|
6507
7200
|
findThreadByRef,
|
|
7201
|
+
findThreadForStackLayer,
|
|
6508
7202
|
flattenTurnInput,
|
|
6509
7203
|
forkChatTab,
|
|
6510
7204
|
forkMessageSlice,
|
|
@@ -6515,6 +7209,8 @@ export {
|
|
|
6515
7209
|
formatGhLandError,
|
|
6516
7210
|
formatIpcInvokeError,
|
|
6517
7211
|
formatMessagesAsTranscript,
|
|
7212
|
+
formatPlanQuestionAnswers,
|
|
7213
|
+
formatPlanQuestionsForChat,
|
|
6518
7214
|
formatRateLimitResetHint,
|
|
6519
7215
|
formatRenameBranchDirective,
|
|
6520
7216
|
formatTranscriptMarkdown,
|
|
@@ -6538,6 +7234,7 @@ export {
|
|
|
6538
7234
|
getPrChecks,
|
|
6539
7235
|
getPrDetails,
|
|
6540
7236
|
getPrMeta,
|
|
7237
|
+
getPrStack,
|
|
6541
7238
|
getRepoSetupInfo,
|
|
6542
7239
|
getRunMode,
|
|
6543
7240
|
getRunScript,
|
|
@@ -6554,9 +7251,12 @@ export {
|
|
|
6554
7251
|
healOrchestrationSoccerTitles,
|
|
6555
7252
|
importConductorWorkspace,
|
|
6556
7253
|
importConductorWorkspaceAsync,
|
|
7254
|
+
initPrStack,
|
|
7255
|
+
initStackFromThread,
|
|
6557
7256
|
initializeGitRepository,
|
|
6558
7257
|
inspectGitWorktree,
|
|
6559
7258
|
installAgent,
|
|
7259
|
+
isAskUserToolName,
|
|
6560
7260
|
isBrightsyConnected,
|
|
6561
7261
|
isBrightsyNdjsonLine,
|
|
6562
7262
|
isCloudCoordinatorThread,
|
|
@@ -6566,14 +7266,17 @@ export {
|
|
|
6566
7266
|
isGlobalRepoPath,
|
|
6567
7267
|
isGlobalThread,
|
|
6568
7268
|
isImageFilePath,
|
|
7269
|
+
isInPrStack,
|
|
6569
7270
|
isLinearConnected,
|
|
6570
7271
|
isOrchestratorCapableAgent,
|
|
6571
7272
|
isOrchestratorThread,
|
|
6572
7273
|
isPidAlive,
|
|
6573
7274
|
isPlaceholderBranch,
|
|
7275
|
+
isPresentPlanToolName,
|
|
6574
7276
|
isSessionQuotaLimit,
|
|
6575
7277
|
isSideboardScratchPath,
|
|
6576
7278
|
isThinkingEffort,
|
|
7279
|
+
isWorkspaceScratchPath,
|
|
6577
7280
|
listAgentSetupInfo,
|
|
6578
7281
|
listBranchCommits,
|
|
6579
7282
|
listBranches,
|
|
@@ -6610,6 +7313,7 @@ export {
|
|
|
6610
7313
|
mcpAllowTools,
|
|
6611
7314
|
mcpAuthWarnings,
|
|
6612
7315
|
mergePr,
|
|
7316
|
+
mergePrStack,
|
|
6613
7317
|
mergeUsage,
|
|
6614
7318
|
nextPastedTextName,
|
|
6615
7319
|
nextThinkingEffort,
|
|
@@ -6619,6 +7323,8 @@ export {
|
|
|
6619
7323
|
normalizeTurnInput,
|
|
6620
7324
|
normalizeWorktreePath,
|
|
6621
7325
|
openInSystemTerminal,
|
|
7326
|
+
openPrStackLayers,
|
|
7327
|
+
openStackLayer,
|
|
6622
7328
|
opencodeAdapter,
|
|
6623
7329
|
orchestrationQuotaFallbackAgent,
|
|
6624
7330
|
orchestrationQuotaOnLimit,
|
|
@@ -6627,15 +7333,19 @@ export {
|
|
|
6627
7333
|
originGhRepoEnv,
|
|
6628
7334
|
parseCursorRunnerLine,
|
|
6629
7335
|
parseForceStopMessage,
|
|
7336
|
+
parseGhStackViewJson,
|
|
6630
7337
|
parseGithubSlugFromRemoteUrl,
|
|
6631
7338
|
parseMcpList,
|
|
7339
|
+
parsePlanQuestionsInput,
|
|
6632
7340
|
parseSessionQuotaResetAt,
|
|
6633
7341
|
partsToAssistantText,
|
|
6634
7342
|
pastedTextStats,
|
|
6635
7343
|
permissionMode,
|
|
7344
|
+
planFileAbs,
|
|
6636
7345
|
previewLand,
|
|
6637
7346
|
pushBranch,
|
|
6638
7347
|
readExistingReviewRequestFile,
|
|
7348
|
+
readPlanFile,
|
|
6639
7349
|
readSkillBody,
|
|
6640
7350
|
readThread,
|
|
6641
7351
|
readWorktreeFile,
|
|
@@ -6647,6 +7357,7 @@ export {
|
|
|
6647
7357
|
repoSlug,
|
|
6648
7358
|
requestReview,
|
|
6649
7359
|
requireAgent,
|
|
7360
|
+
resetGhStackDetectCache,
|
|
6650
7361
|
resolveClaudeExecutable,
|
|
6651
7362
|
resolveConductorCursorAgentId,
|
|
6652
7363
|
resolveCursorModelId,
|
|
@@ -6656,6 +7367,7 @@ export {
|
|
|
6656
7367
|
resolveFilesToCopy,
|
|
6657
7368
|
resolveGhAuthToken,
|
|
6658
7369
|
resolveGithubRepoSlug,
|
|
7370
|
+
resolvePlanMarkdown,
|
|
6659
7371
|
resolvePrSelector,
|
|
6660
7372
|
resolveQuotaFallbackAgent,
|
|
6661
7373
|
resolveRepoRoot,
|
|
@@ -6683,12 +7395,16 @@ export {
|
|
|
6683
7395
|
slugify,
|
|
6684
7396
|
spawnAgentTurn,
|
|
6685
7397
|
splitForCompaction,
|
|
7398
|
+
stackAgentDefaultsFrom,
|
|
7399
|
+
stackIdFrom,
|
|
7400
|
+
stackMergeReadiness,
|
|
6686
7401
|
stageAbsolutePathsAsAttachments,
|
|
6687
7402
|
stageBuffersAsAttachments,
|
|
6688
7403
|
startDevServer,
|
|
6689
7404
|
startMcpServer,
|
|
6690
7405
|
startOrchestration,
|
|
6691
7406
|
stripBrightsyNdjsonNoise,
|
|
7407
|
+
submitPrStack,
|
|
6692
7408
|
suggestSlug,
|
|
6693
7409
|
summarizeConversation,
|
|
6694
7410
|
switchBrightsyAccount,
|
|
@@ -6724,6 +7440,7 @@ export {
|
|
|
6724
7440
|
worktreeNameFromPath,
|
|
6725
7441
|
worktreesRoot,
|
|
6726
7442
|
writeInjectedMcpConfig,
|
|
7443
|
+
writePlanFile,
|
|
6727
7444
|
writeThread,
|
|
6728
7445
|
writeWorktreeFile
|
|
6729
7446
|
};
|