@sideboard-ai/core 0.1.51 → 0.1.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/cursor-runner.cjs +43 -4
- package/dist/agents/cursor-runner.js +45 -6
- package/dist/{agents-HIEJL3UV.js → agents-KP7UJEHJ.js} +1 -1
- package/dist/{agents-5ROTZNCX.js → agents-KYACODJ3.js} +2 -2
- package/dist/{chunk-5ZPSH7VI.js → chunk-A6HVEMIB.js} +16 -7
- package/dist/chunk-B3SJXYIJ.js +24 -0
- package/dist/{chunk-7EUWSBWR.js → chunk-BZST4HMJ.js} +21 -6
- package/dist/{chunk-ZH5QZ4CR.js → chunk-DZFH2KLT.js} +280 -7
- package/dist/chunk-FKOIHGKV.js +21 -0
- package/dist/{chunk-E4VVEKAM.js → chunk-GNML24AW.js} +2 -2
- package/dist/{chunk-EOYDCKQC.js → chunk-HLEX5AQ6.js} +4 -0
- package/dist/{chunk-K3WMKGFY.js → chunk-LRLKJM3O.js} +2 -1
- package/dist/chunk-N5PM7HGQ.js +103 -0
- package/dist/chunk-QTUESPAW.js +101 -0
- package/dist/{chunk-O6W3P7V3.js → chunk-TSRXOSVD.js} +4 -0
- package/dist/{chunk-F4Q3IM6V.js → chunk-UEAHMGHW.js} +2 -1
- package/dist/{chunk-J5JTEJ5O.js → chunk-VG22SETP.js} +6 -0
- package/dist/{chunk-XRSAGVRW.js → chunk-XOU6HNQJ.js} +245 -7
- package/dist/{chunk-O5DOO7DP.js → chunk-XX5BB7NV.js} +3 -3
- package/dist/{chunk-QN7XNQAT.js → chunk-YDXQ72MD.js} +2 -2
- package/dist/{chunk-YFJ4FG2P.js → chunk-YOWIYAVA.js} +3 -3
- package/dist/{coordinator-prompt-7HHJRO7B.js → coordinator-prompt-6FXVTSFN.js} +4 -3
- package/dist/{coordinator-prompt-WD7FAMA2.js → coordinator-prompt-S6JZD5EF.js} +4 -3
- package/dist/{global-workspace-OJEPGDXA.js → global-workspace-EV4G2WMQ.js} +5 -4
- package/dist/{global-workspace-ECYN2MKL.js → global-workspace-MSX2K27Y.js} +5 -4
- package/dist/index.cjs +1383 -149
- package/dist/index.d.cts +389 -15
- package/dist/index.d.ts +389 -15
- package/dist/index.js +883 -91
- package/dist/mcp/run-stdio.cjs +1154 -141
- package/dist/mcp/run-stdio.js +709 -79
- package/dist/plan-file-6O7G4VPQ.js +23 -0
- package/dist/plan-file-PHVKUAEE.js +25 -0
- package/dist/{thread-store-XICUWFNM.js → thread-store-GHOADGL2.js} +1 -1
- package/dist/{thread-store-OV2X6PYO.js → thread-store-UJIGMI5J.js} +1 -1
- package/dist/{workspaces-MUU7RGVV.js → workspaces-3RQQZQRO.js} +6 -5
- package/dist/{workspaces-ZWOOFZUV.js → workspaces-AYTBR6KQ.js} +6 -5
- package/dist/{worktree-DVNDMWZ7.js → worktree-5KEQWSAF.js} +5 -2
- package/dist/{worktree-GDV56MX4.js → worktree-RWGL7FUV.js} +5 -2
- package/package.json +1 -1
package/dist/mcp/run-stdio.js
CHANGED
|
@@ -10,19 +10,25 @@ import {
|
|
|
10
10
|
isSessionQuotaLimit,
|
|
11
11
|
listModelsForAgent,
|
|
12
12
|
looksLikeAgentFailureMessage,
|
|
13
|
+
looksLikeInvalidAgentSession,
|
|
13
14
|
parseBrightsyCliLine,
|
|
14
15
|
parseSessionQuotaResetAt,
|
|
15
16
|
pushTurnStderr,
|
|
16
17
|
resolveQuotaFallbackAgent,
|
|
17
18
|
summarizeTurnStderr
|
|
18
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-BZST4HMJ.js";
|
|
19
20
|
import "../chunk-H6GGDLYS.js";
|
|
20
21
|
import {
|
|
21
22
|
addWorkspace,
|
|
22
23
|
ensureWorkspace,
|
|
23
24
|
removeWorkspace,
|
|
24
25
|
syncWorkspacesFromThreads
|
|
25
|
-
} from "../chunk-
|
|
26
|
+
} from "../chunk-YDXQ72MD.js";
|
|
27
|
+
import {
|
|
28
|
+
extractPresentedPlan,
|
|
29
|
+
readPlanFile,
|
|
30
|
+
writePlanFile
|
|
31
|
+
} from "../chunk-N5PM7HGQ.js";
|
|
26
32
|
import {
|
|
27
33
|
childEnvWithAppSettings,
|
|
28
34
|
getLinearApiKey,
|
|
@@ -41,7 +47,7 @@ import {
|
|
|
41
47
|
isGlobalThread,
|
|
42
48
|
isOrchestratorThread,
|
|
43
49
|
orchestratorSessionPoisonedByBuiltins
|
|
44
|
-
} from "../chunk-
|
|
50
|
+
} from "../chunk-YOWIYAVA.js";
|
|
45
51
|
import {
|
|
46
52
|
assertOrchestratorCapableAgent
|
|
47
53
|
} from "../chunk-S2LL5HA4.js";
|
|
@@ -50,19 +56,24 @@ import {
|
|
|
50
56
|
coordinatorTurnReminder,
|
|
51
57
|
enrichWorkspacesWithGithub,
|
|
52
58
|
ensureGlobalCoordinatorCwd
|
|
53
|
-
} from "../chunk-
|
|
59
|
+
} from "../chunk-UEAHMGHW.js";
|
|
54
60
|
import {
|
|
61
|
+
addPrStackLayer,
|
|
55
62
|
allocateTeamName,
|
|
56
63
|
allocateTeamSlug,
|
|
57
64
|
commitAll,
|
|
65
|
+
createExistingBranchWorktree,
|
|
58
66
|
createOrUpdatePr,
|
|
59
67
|
createThreadWorktree,
|
|
68
|
+
detectGhStack,
|
|
60
69
|
fetchPrHead,
|
|
61
70
|
formatGhLandError,
|
|
62
71
|
getPr,
|
|
63
72
|
getPrChecks,
|
|
64
73
|
getPrDetails,
|
|
65
74
|
getPrMeta,
|
|
75
|
+
getPrStack,
|
|
76
|
+
initPrStack,
|
|
66
77
|
isDirty,
|
|
67
78
|
isPlaceholderBranch,
|
|
68
79
|
isSideboardScratchPath,
|
|
@@ -82,7 +93,12 @@ import {
|
|
|
82
93
|
takenSlugsFromThread,
|
|
83
94
|
threadDisplayLabel,
|
|
84
95
|
worktreeNameFromPath
|
|
85
|
-
} from "../chunk-
|
|
96
|
+
} from "../chunk-XOU6HNQJ.js";
|
|
97
|
+
import {
|
|
98
|
+
ATTACHMENTS_DIR,
|
|
99
|
+
LEGACY_ATTACHMENTS_DIR,
|
|
100
|
+
attachmentsGitignoreBody
|
|
101
|
+
} from "../chunk-B3SJXYIJ.js";
|
|
86
102
|
import {
|
|
87
103
|
appendMessage,
|
|
88
104
|
createEmptyThread,
|
|
@@ -94,7 +110,7 @@ import {
|
|
|
94
110
|
updateThread,
|
|
95
111
|
withThreadLock,
|
|
96
112
|
writeThread
|
|
97
|
-
} from "../chunk-
|
|
113
|
+
} from "../chunk-HLEX5AQ6.js";
|
|
98
114
|
import {
|
|
99
115
|
ensureAgentPath,
|
|
100
116
|
gh,
|
|
@@ -119,7 +135,7 @@ import { basename as basename4 } from "path";
|
|
|
119
135
|
|
|
120
136
|
// src/orchestrator/orchestrator.ts
|
|
121
137
|
import { EventEmitter } from "events";
|
|
122
|
-
import { existsSync as
|
|
138
|
+
import { existsSync as existsSync13 } from "fs";
|
|
123
139
|
|
|
124
140
|
// src/agents/spawn.ts
|
|
125
141
|
import { createInterface } from "readline";
|
|
@@ -161,6 +177,9 @@ function toolDescription(name, input) {
|
|
|
161
177
|
if (/present_artifact$/i.test(name)) {
|
|
162
178
|
return str(input?.title) ? `Present ${str(input?.title)}` : "Present artifact";
|
|
163
179
|
}
|
|
180
|
+
if (/present_plan$/i.test(name)) {
|
|
181
|
+
return str(input?.title) ? `Plan ${str(input?.title)}` : "Present plan";
|
|
182
|
+
}
|
|
164
183
|
if (/present_schema$/i.test(name)) {
|
|
165
184
|
return str(input?.title) ? `Schema ${str(input?.title)}` : "Present schema";
|
|
166
185
|
}
|
|
@@ -357,9 +376,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
357
376
|
`Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
|
|
358
377
|
);
|
|
359
378
|
}
|
|
360
|
-
const { isGlobalThread: isGlobalThread2 } = await import("../global-workspace-
|
|
379
|
+
const { isGlobalThread: isGlobalThread2 } = await import("../global-workspace-EV4G2WMQ.js");
|
|
361
380
|
if (isGlobalThread2(thread)) {
|
|
362
|
-
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("../coordinator-prompt-
|
|
381
|
+
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("../coordinator-prompt-6FXVTSFN.js");
|
|
363
382
|
ensureGlobalCoordinatorCwd2();
|
|
364
383
|
}
|
|
365
384
|
if (isOrchestratorThread(thread)) {
|
|
@@ -1083,7 +1102,7 @@ async function requireAgent(agent, opts) {
|
|
|
1083
1102
|
}
|
|
1084
1103
|
|
|
1085
1104
|
// src/threads/create.ts
|
|
1086
|
-
async function createThread(input,
|
|
1105
|
+
async function createThread(input, _onSetupLine) {
|
|
1087
1106
|
await requireAgent(input.agent);
|
|
1088
1107
|
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
1089
1108
|
if (!existsSync5(repoPath)) {
|
|
@@ -1141,26 +1160,10 @@ async function createThread(input, onSetupLine) {
|
|
|
1141
1160
|
});
|
|
1142
1161
|
writeThread(thread);
|
|
1143
1162
|
await ensureWorkspace(repoPath);
|
|
1144
|
-
try {
|
|
1145
|
-
let setup = await runSetupScript(repoPath, worktreePath, onSetupLine);
|
|
1146
|
-
if (!setup.ran) {
|
|
1147
|
-
setup = await runCursorWorktreeSetup(repoPath, worktreePath, onSetupLine);
|
|
1148
|
-
}
|
|
1149
|
-
if (setup.ran && setup.exitCode !== 0 && setup.exitCode !== null) {
|
|
1150
|
-
updateThread(thread.id, {
|
|
1151
|
-
lastError: `Setup exited ${setup.exitCode} (thread is still usable)`
|
|
1152
|
-
});
|
|
1153
|
-
}
|
|
1154
|
-
} catch (err) {
|
|
1155
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1156
|
-
updateThread(thread.id, {
|
|
1157
|
-
lastError: `Setup failed: ${message}`
|
|
1158
|
-
});
|
|
1159
|
-
}
|
|
1160
1163
|
return readThread(thread.id) ?? thread;
|
|
1161
1164
|
}
|
|
1162
1165
|
async function listLinearIssues(agent, repoPath) {
|
|
1163
|
-
const { getAdapter: getAdapter2 } = await import("../agents-
|
|
1166
|
+
const { getAdapter: getAdapter2 } = await import("../agents-KP7UJEHJ.js");
|
|
1164
1167
|
await requireAgent(agent, { requireLinear: true });
|
|
1165
1168
|
const adapter = getAdapter2(agent);
|
|
1166
1169
|
if (!adapter.listLinearIssues) {
|
|
@@ -1477,7 +1480,9 @@ function worktreeBindingFrom(from) {
|
|
|
1477
1480
|
sourceIsFork: from.sourceIsFork,
|
|
1478
1481
|
parentThreadId: from.parentThreadId,
|
|
1479
1482
|
prUrl: from.prUrl,
|
|
1480
|
-
prTitle: from.prTitle
|
|
1483
|
+
prTitle: from.prTitle,
|
|
1484
|
+
stackId: from.stackId,
|
|
1485
|
+
stackLayer: from.stackLayer
|
|
1481
1486
|
};
|
|
1482
1487
|
}
|
|
1483
1488
|
function threadsSharingWorktree(worktreePath) {
|
|
@@ -1675,13 +1680,10 @@ File: src/client/frontends/desktop/core/UserData.ts
|
|
|
1675
1680
|
// src/review/request-review.ts
|
|
1676
1681
|
var REPO_REVIEW_PATH = ".sideboard/review.md";
|
|
1677
1682
|
var REPO_REVIEW_NAME = "review.md";
|
|
1678
|
-
var REVIEW_REQUEST_PATH =
|
|
1683
|
+
var REVIEW_REQUEST_PATH = `${ATTACHMENTS_DIR}/Review request.md`;
|
|
1684
|
+
var LEGACY_REVIEW_REQUEST_PATH = `${LEGACY_ATTACHMENTS_DIR}/Review request.md`;
|
|
1679
1685
|
var REVIEW_REQUEST_NAME = "Review request.md";
|
|
1680
1686
|
var REVIEW_REQUEST_PREFILL = "Review.";
|
|
1681
|
-
var ATTACHMENTS_GITIGNORE = `# Sideboard review / composer attachments (local only)
|
|
1682
|
-
*
|
|
1683
|
-
!.gitignore
|
|
1684
|
-
`;
|
|
1685
1687
|
var LEGACY_REVIEW_TEMPLATE_MARKERS = [
|
|
1686
1688
|
"You are acting as a reviewer for a proposed code change made by another engineer.",
|
|
1687
1689
|
"HOW MANY FINDINGS TO RETURN:"
|
|
@@ -1704,10 +1706,10 @@ function readTextIfPresent(abs) {
|
|
|
1704
1706
|
}
|
|
1705
1707
|
}
|
|
1706
1708
|
function ensureAttachmentsGitignore(worktreePath) {
|
|
1707
|
-
const gitignoreAbs = join5(worktreePath,
|
|
1709
|
+
const gitignoreAbs = join5(worktreePath, ATTACHMENTS_DIR, ".gitignore");
|
|
1708
1710
|
if (existsSync6(gitignoreAbs)) return;
|
|
1709
1711
|
mkdirSync2(dirname2(gitignoreAbs), { recursive: true });
|
|
1710
|
-
writeFileSync(gitignoreAbs,
|
|
1712
|
+
writeFileSync(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
|
|
1711
1713
|
}
|
|
1712
1714
|
function resolveReviewGuidelines(worktreePath) {
|
|
1713
1715
|
const repoAbs = join5(worktreePath, REPO_REVIEW_PATH);
|
|
@@ -1730,6 +1732,16 @@ function resolveReviewGuidelines(worktreePath) {
|
|
|
1730
1732
|
source: "local"
|
|
1731
1733
|
};
|
|
1732
1734
|
}
|
|
1735
|
+
const legacyAbs = join5(worktreePath, LEGACY_REVIEW_REQUEST_PATH);
|
|
1736
|
+
const legacyContent = readTextIfPresent(legacyAbs);
|
|
1737
|
+
if (legacyContent && !shouldRefreshReviewRequestTemplate(legacyContent)) {
|
|
1738
|
+
return {
|
|
1739
|
+
path: LEGACY_REVIEW_REQUEST_PATH,
|
|
1740
|
+
name: REVIEW_REQUEST_NAME,
|
|
1741
|
+
content: legacyContent,
|
|
1742
|
+
source: "local"
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1733
1745
|
ensureAttachmentsGitignore(worktreePath);
|
|
1734
1746
|
mkdirSync2(dirname2(localAbs), { recursive: true });
|
|
1735
1747
|
writeFileSync(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
|
|
@@ -2013,7 +2025,7 @@ async function adoptThread(input) {
|
|
|
2013
2025
|
messages: input.messages ?? []
|
|
2014
2026
|
});
|
|
2015
2027
|
writeThread(thread);
|
|
2016
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-
|
|
2028
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-3RQQZQRO.js");
|
|
2017
2029
|
await ensureWorkspace2(repoPath);
|
|
2018
2030
|
return thread;
|
|
2019
2031
|
}
|
|
@@ -2208,11 +2220,295 @@ async function importConductorWorkspaceAsync(workspaceId) {
|
|
|
2208
2220
|
return importConductorWorkspace(workspaceId);
|
|
2209
2221
|
}
|
|
2210
2222
|
|
|
2223
|
+
// src/threads/stack-layers.ts
|
|
2224
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2225
|
+
function stackIdFrom(stack) {
|
|
2226
|
+
if (stack.stackNumber != null) return `gh-stack-${stack.stackNumber}`;
|
|
2227
|
+
const key = stack.layers.map((l) => l.branchName).join("|");
|
|
2228
|
+
if (!key) return null;
|
|
2229
|
+
return `gh-stack-local-${hashShort(key)}`;
|
|
2230
|
+
}
|
|
2231
|
+
function hashShort(s) {
|
|
2232
|
+
let h = 0;
|
|
2233
|
+
for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) | 0;
|
|
2234
|
+
return Math.abs(h).toString(36);
|
|
2235
|
+
}
|
|
2236
|
+
function requireThread3(idOrRef) {
|
|
2237
|
+
const thread = findThreadByRef(idOrRef) ?? readThread(idOrRef);
|
|
2238
|
+
if (!thread) throw new Error(`Thread not found: ${idOrRef}`);
|
|
2239
|
+
return thread;
|
|
2240
|
+
}
|
|
2241
|
+
function sanitizeSlugPart(name) {
|
|
2242
|
+
return name.trim().replace(/^refs\/heads\//, "").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "layer";
|
|
2243
|
+
}
|
|
2244
|
+
function layerSlug(stackId, layer) {
|
|
2245
|
+
const stackPart = stackId.replace(/^gh-stack-/, "s");
|
|
2246
|
+
return sanitizeSlugPart(`${stackPart}-L${layer.position}-${layer.branchName}`);
|
|
2247
|
+
}
|
|
2248
|
+
function findThreadForStackLayer(repoPath, stackId, layer) {
|
|
2249
|
+
const threads = listThreads({ includeArchived: false }).filter(
|
|
2250
|
+
(t) => t.repoPath === repoPath && t.stackId === stackId
|
|
2251
|
+
);
|
|
2252
|
+
const byLayer = threads.find((t) => t.stackLayer === layer.position);
|
|
2253
|
+
if (byLayer) return byLayer;
|
|
2254
|
+
return threads.find((t) => t.branchName === layer.branchName) ?? listThreads({ includeArchived: false }).find(
|
|
2255
|
+
(t) => t.repoPath === repoPath && t.branchName === layer.branchName
|
|
2256
|
+
) ?? null;
|
|
2257
|
+
}
|
|
2258
|
+
async function openStackLayer(input, _onSetupLine) {
|
|
2259
|
+
await requireAgent(input.agent);
|
|
2260
|
+
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
2261
|
+
const stackId = stackIdFrom(input.stack);
|
|
2262
|
+
if (!stackId) throw new Error("Cannot open stack layer without a stack id");
|
|
2263
|
+
const existing = findThreadForStackLayer(repoPath, stackId, input.layer);
|
|
2264
|
+
if (existing) {
|
|
2265
|
+
const patch = {};
|
|
2266
|
+
if (existing.stackId !== stackId) patch.stackId = stackId;
|
|
2267
|
+
if (existing.stackLayer !== input.layer.position) {
|
|
2268
|
+
patch.stackLayer = input.layer.position;
|
|
2269
|
+
}
|
|
2270
|
+
if (input.layer.prUrl && existing.prUrl !== input.layer.prUrl) {
|
|
2271
|
+
patch.prUrl = input.layer.prUrl;
|
|
2272
|
+
}
|
|
2273
|
+
if (input.layer.title && existing.prTitle !== input.layer.title) {
|
|
2274
|
+
patch.prTitle = input.layer.title;
|
|
2275
|
+
}
|
|
2276
|
+
if (Object.keys(patch).length > 0) updateThread(existing.id, patch);
|
|
2277
|
+
return {
|
|
2278
|
+
thread: readThread(existing.id) ?? existing,
|
|
2279
|
+
createdWorktree: false
|
|
2280
|
+
};
|
|
2281
|
+
}
|
|
2282
|
+
let worktreePath = null;
|
|
2283
|
+
let branchName = input.layer.branchName;
|
|
2284
|
+
let createdWorktree = false;
|
|
2285
|
+
const trees = await listWorktrees(repoPath);
|
|
2286
|
+
const checkedOut = trees.find((w) => w.branch === branchName);
|
|
2287
|
+
if (checkedOut?.path && existsSync8(checkedOut.path)) {
|
|
2288
|
+
if (input.reuseExistingWorktree !== false) {
|
|
2289
|
+
worktreePath = checkedOut.path;
|
|
2290
|
+
} else {
|
|
2291
|
+
throw new Error(
|
|
2292
|
+
`Branch ${branchName} is already checked out at ${checkedOut.path}`
|
|
2293
|
+
);
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
if (!worktreePath) {
|
|
2297
|
+
const slug = layerSlug(stackId, input.layer);
|
|
2298
|
+
const created = await createExistingBranchWorktree({
|
|
2299
|
+
repoPath,
|
|
2300
|
+
branchName,
|
|
2301
|
+
slug
|
|
2302
|
+
});
|
|
2303
|
+
worktreePath = created.worktreePath;
|
|
2304
|
+
branchName = created.branchName;
|
|
2305
|
+
copyConfiguredFiles(repoPath, worktreePath);
|
|
2306
|
+
createdWorktree = true;
|
|
2307
|
+
}
|
|
2308
|
+
const title = input.layer.title?.trim() || (input.layer.prNumber != null ? `PR #${input.layer.prNumber}` : input.layer.branchName);
|
|
2309
|
+
const thread = createEmptyThread({
|
|
2310
|
+
title,
|
|
2311
|
+
userSetTitle: Boolean(input.layer.title?.trim()),
|
|
2312
|
+
sourceType: input.layer.prNumber != null ? "pr" : "branch",
|
|
2313
|
+
sourceRef: input.layer.prNumber != null ? String(input.layer.prNumber) : input.layer.branchName,
|
|
2314
|
+
branchName,
|
|
2315
|
+
worktreePath,
|
|
2316
|
+
repoPath,
|
|
2317
|
+
agent: input.agent,
|
|
2318
|
+
autonomy: input.autonomy ?? "default",
|
|
2319
|
+
model: input.model ?? null,
|
|
2320
|
+
effort: input.effort ?? "high",
|
|
2321
|
+
fast: Boolean(input.fast),
|
|
2322
|
+
planMode: Boolean(input.planMode),
|
|
2323
|
+
parentThreadId: input.parentThreadId ?? null,
|
|
2324
|
+
status: "idle",
|
|
2325
|
+
prUrl: input.layer.prUrl,
|
|
2326
|
+
prTitle: input.layer.title ?? null,
|
|
2327
|
+
stackId,
|
|
2328
|
+
stackLayer: input.layer.position
|
|
2329
|
+
});
|
|
2330
|
+
writeThread(thread);
|
|
2331
|
+
await ensureWorkspace(repoPath);
|
|
2332
|
+
return { thread: readThread(thread.id) ?? thread, createdWorktree };
|
|
2333
|
+
}
|
|
2334
|
+
async function openPrStackLayers(input, onSetupLine) {
|
|
2335
|
+
const from = requireThread3(input.threadRef);
|
|
2336
|
+
if (!from.worktreePath?.trim() || !from.repoPath?.trim()) {
|
|
2337
|
+
throw new Error("Thread has no worktree");
|
|
2338
|
+
}
|
|
2339
|
+
const stack = await getPrStack(from.worktreePath);
|
|
2340
|
+
if (!stack) throw new Error("Current branch is not part of a GitHub PR stack");
|
|
2341
|
+
const layers = input.layer != null ? stack.layers.filter((l) => l.position === input.layer) : stack.layers;
|
|
2342
|
+
if (!layers.length) {
|
|
2343
|
+
throw new Error(
|
|
2344
|
+
input.layer != null ? `No stack layer at position ${input.layer}` : "Stack has no layers"
|
|
2345
|
+
);
|
|
2346
|
+
}
|
|
2347
|
+
const threads = [];
|
|
2348
|
+
const createdThreadIds = [];
|
|
2349
|
+
for (const layer of layers) {
|
|
2350
|
+
const { thread, createdWorktree } = await openStackLayer(
|
|
2351
|
+
{
|
|
2352
|
+
repoPath: from.repoPath,
|
|
2353
|
+
stack,
|
|
2354
|
+
layer,
|
|
2355
|
+
agent: from.agent,
|
|
2356
|
+
autonomy: from.autonomy,
|
|
2357
|
+
model: from.model,
|
|
2358
|
+
effort: from.effort,
|
|
2359
|
+
fast: from.fast,
|
|
2360
|
+
planMode: from.planMode,
|
|
2361
|
+
parentThreadId: from.id
|
|
2362
|
+
},
|
|
2363
|
+
onSetupLine
|
|
2364
|
+
);
|
|
2365
|
+
threads.push(thread);
|
|
2366
|
+
if (createdWorktree) createdThreadIds.push(thread.id);
|
|
2367
|
+
}
|
|
2368
|
+
const stackId = stackIdFrom(stack);
|
|
2369
|
+
const current = stack.layers[stack.currentIndex];
|
|
2370
|
+
if (stackId) {
|
|
2371
|
+
updateThread(from.id, {
|
|
2372
|
+
stackId,
|
|
2373
|
+
stackLayer: current?.position ?? from.stackLayer
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
return { stack, threads, createdThreadIds };
|
|
2377
|
+
}
|
|
2378
|
+
async function addStackLayerFromThread(input, onSetupLine) {
|
|
2379
|
+
const from = requireThread3(input.threadRef);
|
|
2380
|
+
if (!from.worktreePath?.trim() || !from.repoPath?.trim()) {
|
|
2381
|
+
throw new Error("Thread has no worktree");
|
|
2382
|
+
}
|
|
2383
|
+
const status = await detectGhStack(from.worktreePath);
|
|
2384
|
+
if (!status.available) throw new Error(status.reason);
|
|
2385
|
+
await addPrStackLayer(from.worktreePath, input.branchName);
|
|
2386
|
+
const stack = await getPrStack(from.worktreePath);
|
|
2387
|
+
if (!stack) throw new Error("Stack not found after adding layer");
|
|
2388
|
+
const layer = stack.layers.find((l) => l.branchName === input.branchName.trim()) ?? stack.layers[stack.layers.length - 1];
|
|
2389
|
+
if (!layer) throw new Error("New stack layer not found");
|
|
2390
|
+
if (input.title?.trim()) {
|
|
2391
|
+
layer.title = input.title.trim();
|
|
2392
|
+
}
|
|
2393
|
+
const thread = await openStackLayer(
|
|
2394
|
+
{
|
|
2395
|
+
repoPath: from.repoPath,
|
|
2396
|
+
stack,
|
|
2397
|
+
layer,
|
|
2398
|
+
agent: from.agent,
|
|
2399
|
+
autonomy: from.autonomy,
|
|
2400
|
+
model: from.model,
|
|
2401
|
+
effort: from.effort,
|
|
2402
|
+
fast: from.fast,
|
|
2403
|
+
planMode: from.planMode,
|
|
2404
|
+
parentThreadId: from.id
|
|
2405
|
+
},
|
|
2406
|
+
onSetupLine
|
|
2407
|
+
);
|
|
2408
|
+
return { stack, thread: thread.thread, createdWorktree: thread.createdWorktree };
|
|
2409
|
+
}
|
|
2410
|
+
async function initStackFromThread(input, onSetupLine) {
|
|
2411
|
+
const from = requireThread3(input.threadRef);
|
|
2412
|
+
if (!from.worktreePath?.trim() || !from.branchName?.trim() || !from.repoPath?.trim()) {
|
|
2413
|
+
throw new Error("Thread has no worktree/branch");
|
|
2414
|
+
}
|
|
2415
|
+
const status = await detectGhStack(from.worktreePath);
|
|
2416
|
+
if (!status.available) throw new Error(status.reason);
|
|
2417
|
+
const branches = [
|
|
2418
|
+
from.branchName,
|
|
2419
|
+
...(input.additionalBranches ?? []).map((b) => b.trim()).filter(Boolean)
|
|
2420
|
+
];
|
|
2421
|
+
await initPrStack(from.worktreePath, branches, {
|
|
2422
|
+
base: input.base ?? await resolveDefaultBranch(from.repoPath)
|
|
2423
|
+
});
|
|
2424
|
+
return openPrStackLayers({ threadRef: from.id }, onSetupLine);
|
|
2425
|
+
}
|
|
2426
|
+
async function createPrStack(input, onSetupLine) {
|
|
2427
|
+
await requireAgent(input.agent);
|
|
2428
|
+
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
2429
|
+
if (!existsSync8(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
|
|
2430
|
+
if (!input.branches.length) throw new Error("At least one branch name required");
|
|
2431
|
+
const status = await detectGhStack(repoPath);
|
|
2432
|
+
if (!status.available) throw new Error(status.reason);
|
|
2433
|
+
const team = allocateTeamSlug(repoPath);
|
|
2434
|
+
const base = input.base ?? await resolveDefaultBranch(repoPath);
|
|
2435
|
+
const bootstrap = await createThreadWorktree({
|
|
2436
|
+
repoPath,
|
|
2437
|
+
sourceRef: base,
|
|
2438
|
+
slug: `${team.slug}-stack-init`
|
|
2439
|
+
});
|
|
2440
|
+
copyConfiguredFiles(repoPath, bootstrap.worktreePath);
|
|
2441
|
+
try {
|
|
2442
|
+
await initPrStack(bootstrap.worktreePath, input.branches, { base });
|
|
2443
|
+
const bottom = input.branches[0];
|
|
2444
|
+
const co = await git(["checkout", bottom], bootstrap.worktreePath, {
|
|
2445
|
+
reject: false
|
|
2446
|
+
});
|
|
2447
|
+
if (co.exitCode !== 0) {
|
|
2448
|
+
throw new Error(
|
|
2449
|
+
co.stderr.trim() || co.stdout.trim() || `Could not check out ${bottom} after stack init`
|
|
2450
|
+
);
|
|
2451
|
+
}
|
|
2452
|
+
} catch (err) {
|
|
2453
|
+
try {
|
|
2454
|
+
await removeWorktree(repoPath, bootstrap.worktreePath, {
|
|
2455
|
+
deleteBranch: bootstrap.branchName
|
|
2456
|
+
});
|
|
2457
|
+
} catch {
|
|
2458
|
+
}
|
|
2459
|
+
throw err;
|
|
2460
|
+
}
|
|
2461
|
+
const stack = await getPrStack(bootstrap.worktreePath);
|
|
2462
|
+
if (!stack) {
|
|
2463
|
+
try {
|
|
2464
|
+
await removeWorktree(repoPath, bootstrap.worktreePath, {
|
|
2465
|
+
deleteBranch: bootstrap.branchName
|
|
2466
|
+
});
|
|
2467
|
+
} catch {
|
|
2468
|
+
}
|
|
2469
|
+
throw new Error("Stack init succeeded but gh stack view returned no stack");
|
|
2470
|
+
}
|
|
2471
|
+
const threads = [];
|
|
2472
|
+
const createdThreadIds = [];
|
|
2473
|
+
for (const layer of stack.layers) {
|
|
2474
|
+
const title = layer.position === 1 && input.title?.trim() ? input.title.trim() : layer.title;
|
|
2475
|
+
const opened = await openStackLayer(
|
|
2476
|
+
{
|
|
2477
|
+
repoPath,
|
|
2478
|
+
stack,
|
|
2479
|
+
layer: title ? { ...layer, title } : layer,
|
|
2480
|
+
agent: input.agent,
|
|
2481
|
+
autonomy: input.autonomy,
|
|
2482
|
+
model: input.model,
|
|
2483
|
+
effort: input.effort,
|
|
2484
|
+
fast: input.fast,
|
|
2485
|
+
planMode: input.planMode,
|
|
2486
|
+
reuseExistingWorktree: true
|
|
2487
|
+
},
|
|
2488
|
+
onSetupLine
|
|
2489
|
+
);
|
|
2490
|
+
threads.push(opened.thread);
|
|
2491
|
+
if (opened.createdWorktree || opened.thread.worktreePath === bootstrap.worktreePath) {
|
|
2492
|
+
createdThreadIds.push(opened.thread.id);
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
const claimed = new Set(threads.map((t) => t.worktreePath));
|
|
2496
|
+
if (!claimed.has(bootstrap.worktreePath) && existsSync8(bootstrap.worktreePath)) {
|
|
2497
|
+
try {
|
|
2498
|
+
await removeWorktree(repoPath, bootstrap.worktreePath, {
|
|
2499
|
+
deleteBranch: bootstrap.branchName
|
|
2500
|
+
});
|
|
2501
|
+
} catch {
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
return { stack, threads, createdThreadIds };
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2211
2507
|
// src/diff/diff.ts
|
|
2212
|
-
import { existsSync as
|
|
2508
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync5, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2213
2509
|
import { dirname as dirname3, join as join7 } from "path";
|
|
2214
2510
|
async function inspectGitWorktree(worktreePath) {
|
|
2215
|
-
if (!worktreePath || !
|
|
2511
|
+
if (!worktreePath || !existsSync9(worktreePath)) return "missing_worktree";
|
|
2216
2512
|
const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
|
|
2217
2513
|
reject: false
|
|
2218
2514
|
});
|
|
@@ -2220,7 +2516,7 @@ async function inspectGitWorktree(worktreePath) {
|
|
|
2220
2516
|
return "ok";
|
|
2221
2517
|
}
|
|
2222
2518
|
async function initializeGitRepository(worktreePath) {
|
|
2223
|
-
if (!worktreePath || !
|
|
2519
|
+
if (!worktreePath || !existsSync9(worktreePath)) {
|
|
2224
2520
|
throw new Error("Worktree not found");
|
|
2225
2521
|
}
|
|
2226
2522
|
const status = await inspectGitWorktree(worktreePath);
|
|
@@ -2904,7 +3200,7 @@ async function confirmLand(thread, opts) {
|
|
|
2904
3200
|
}
|
|
2905
3201
|
|
|
2906
3202
|
// src/skills/discover.ts
|
|
2907
|
-
import { existsSync as
|
|
3203
|
+
import { existsSync as existsSync10, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
|
|
2908
3204
|
import { homedir } from "os";
|
|
2909
3205
|
import { join as join8 } from "path";
|
|
2910
3206
|
function toCommand(name) {
|
|
@@ -2957,7 +3253,7 @@ function readSkill(skillMd, source) {
|
|
|
2957
3253
|
}
|
|
2958
3254
|
}
|
|
2959
3255
|
function scanSkillsDir(dir, source, out) {
|
|
2960
|
-
if (!
|
|
3256
|
+
if (!existsSync10(dir)) return;
|
|
2961
3257
|
let entries;
|
|
2962
3258
|
try {
|
|
2963
3259
|
entries = readdirSync4(dir);
|
|
@@ -2967,7 +3263,7 @@ function scanSkillsDir(dir, source, out) {
|
|
|
2967
3263
|
for (const entry of entries) {
|
|
2968
3264
|
if (entry.startsWith(".")) continue;
|
|
2969
3265
|
const skillMd = join8(dir, entry, "SKILL.md");
|
|
2970
|
-
if (!
|
|
3266
|
+
if (!existsSync10(skillMd)) continue;
|
|
2971
3267
|
try {
|
|
2972
3268
|
if (!statSync3(skillMd).isFile()) continue;
|
|
2973
3269
|
} catch {
|
|
@@ -2978,7 +3274,7 @@ function scanSkillsDir(dir, source, out) {
|
|
|
2978
3274
|
}
|
|
2979
3275
|
}
|
|
2980
3276
|
function scanClaudePluginSkills(pluginsRoot, out) {
|
|
2981
|
-
if (!
|
|
3277
|
+
if (!existsSync10(pluginsRoot)) return;
|
|
2982
3278
|
const walk = (dir, depth, lookingForSkillsDir) => {
|
|
2983
3279
|
if (depth > 7) return;
|
|
2984
3280
|
let entries;
|
|
@@ -3128,7 +3424,7 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
|
|
|
3128
3424
|
}
|
|
3129
3425
|
|
|
3130
3426
|
// src/composer/stage-files.ts
|
|
3131
|
-
import { copyFileSync as copyFileSync3, existsSync as
|
|
3427
|
+
import { copyFileSync as copyFileSync3, existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync7, statSync as statSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
3132
3428
|
import { basename as basename3, extname, join as join9 } from "path";
|
|
3133
3429
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
3134
3430
|
var IMAGE_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
@@ -3151,11 +3447,6 @@ var IMAGE_MIME_BY_EXT = {
|
|
|
3151
3447
|
bmp: "image/bmp",
|
|
3152
3448
|
ico: "image/x-icon"
|
|
3153
3449
|
};
|
|
3154
|
-
var ATTACHMENTS_DIR = ".sideboard/attachments";
|
|
3155
|
-
var ATTACHMENTS_GITIGNORE2 = `# Sideboard review / composer attachments (local only)
|
|
3156
|
-
*
|
|
3157
|
-
!.gitignore
|
|
3158
|
-
`;
|
|
3159
3450
|
var MAX_INLINE_BYTES = 4e5;
|
|
3160
3451
|
var MAX_PREVIEW_BYTES = 5e6;
|
|
3161
3452
|
function fileExtension(filePath) {
|
|
@@ -3172,19 +3463,19 @@ function ensureAttachmentsDir(worktreePath) {
|
|
|
3172
3463
|
const dir = join9(worktreePath, ATTACHMENTS_DIR);
|
|
3173
3464
|
mkdirSync4(dir, { recursive: true });
|
|
3174
3465
|
const gi = join9(dir, ".gitignore");
|
|
3175
|
-
if (!
|
|
3176
|
-
writeFileSync3(gi,
|
|
3466
|
+
if (!existsSync11(gi)) {
|
|
3467
|
+
writeFileSync3(gi, attachmentsGitignoreBody(), "utf8");
|
|
3177
3468
|
}
|
|
3178
3469
|
return dir;
|
|
3179
3470
|
}
|
|
3180
3471
|
function uniqueAttachmentName(dir, originalName) {
|
|
3181
3472
|
const safe = originalName.replace(/[/\\]/g, "_") || "file";
|
|
3182
|
-
if (!
|
|
3473
|
+
if (!existsSync11(join9(dir, safe))) return safe;
|
|
3183
3474
|
const ext = extname(safe);
|
|
3184
3475
|
const stem = ext ? safe.slice(0, -ext.length) : safe;
|
|
3185
3476
|
for (let i = 1; i < 1e4; i++) {
|
|
3186
3477
|
const candidate = `${stem}-${i}${ext}`;
|
|
3187
|
-
if (!
|
|
3478
|
+
if (!existsSync11(join9(dir, candidate))) return candidate;
|
|
3188
3479
|
}
|
|
3189
3480
|
return `${stem}-${randomUUID4()}${ext}`;
|
|
3190
3481
|
}
|
|
@@ -3317,7 +3608,7 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
|
|
|
3317
3608
|
}
|
|
3318
3609
|
|
|
3319
3610
|
// src/agents/instructions.ts
|
|
3320
|
-
import { existsSync as
|
|
3611
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
|
|
3321
3612
|
import { join as join10 } from "path";
|
|
3322
3613
|
function normPath(p) {
|
|
3323
3614
|
return p.replace(/\/+$/, "");
|
|
@@ -3457,7 +3748,7 @@ function loadAgentInstructions(worktreePath, agent) {
|
|
|
3457
3748
|
for (const rel of candidates) {
|
|
3458
3749
|
if (seen.has(rel)) continue;
|
|
3459
3750
|
const abs = join10(worktreePath, rel);
|
|
3460
|
-
if (!
|
|
3751
|
+
if (!existsSync12(abs)) continue;
|
|
3461
3752
|
try {
|
|
3462
3753
|
if (!statSync5(abs).isFile()) continue;
|
|
3463
3754
|
let content = readFileSync8(abs, "utf8");
|
|
@@ -3602,7 +3893,7 @@ var Orchestrator = class {
|
|
|
3602
3893
|
}
|
|
3603
3894
|
continue;
|
|
3604
3895
|
}
|
|
3605
|
-
if (!
|
|
3896
|
+
if (!existsSync13(thread.worktreePath)) {
|
|
3606
3897
|
setStatus(thread.id, "broken", "Worktree missing on disk");
|
|
3607
3898
|
this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
|
|
3608
3899
|
continue;
|
|
@@ -3747,26 +4038,43 @@ var Orchestrator = class {
|
|
|
3747
4038
|
return findThreadByRef(idOrRef) ?? readThread(idOrRef);
|
|
3748
4039
|
}
|
|
3749
4040
|
async createThread(input) {
|
|
3750
|
-
|
|
3751
|
-
this.emit({
|
|
3752
|
-
type: "turn_output",
|
|
3753
|
-
threadId: "pending",
|
|
3754
|
-
event: { type: "stdout", data: line }
|
|
3755
|
-
});
|
|
3756
|
-
});
|
|
4041
|
+
const thread = await createThread(input);
|
|
3757
4042
|
this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
|
|
4043
|
+
void this.finishCreateThread(thread.id, input.prompt?.trim() || void 0);
|
|
4044
|
+
return thread;
|
|
4045
|
+
}
|
|
4046
|
+
async finishCreateThread(threadId, prompt) {
|
|
4047
|
+
await this.runSetupAfterCreate(threadId);
|
|
3758
4048
|
const { autoRunAfterSetupEnabled } = await import("../app-settings-7XVDQJ7F.js");
|
|
3759
4049
|
if (autoRunAfterSetupEnabled()) {
|
|
3760
4050
|
try {
|
|
3761
|
-
await this.startDev(
|
|
4051
|
+
await this.startDev(threadId);
|
|
3762
4052
|
} catch {
|
|
3763
4053
|
}
|
|
3764
4054
|
}
|
|
3765
|
-
const prompt = input.prompt?.trim();
|
|
3766
4055
|
if (prompt) {
|
|
3767
|
-
|
|
4056
|
+
try {
|
|
4057
|
+
await this.send(threadId, prompt);
|
|
4058
|
+
} catch (err) {
|
|
4059
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4060
|
+
updateThread(threadId, {
|
|
4061
|
+
lastError: `First prompt failed: ${message}`
|
|
4062
|
+
});
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
/** Run workspace setup after a new worktree is created (no-op if none configured). */
|
|
4067
|
+
async runSetupAfterCreate(threadId) {
|
|
4068
|
+
try {
|
|
4069
|
+
await this.runSetup(threadId);
|
|
4070
|
+
} catch (err) {
|
|
4071
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4072
|
+
if (/no setup script/i.test(message)) return;
|
|
4073
|
+
if (/already running/i.test(message)) return;
|
|
4074
|
+
updateThread(threadId, {
|
|
4075
|
+
lastError: `Setup failed: ${message}`
|
|
4076
|
+
});
|
|
3768
4077
|
}
|
|
3769
|
-
return thread;
|
|
3770
4078
|
}
|
|
3771
4079
|
listWorkspaces() {
|
|
3772
4080
|
const fromThreads = listThreads({ includeArchived: false }).map((t) => t.repoPath);
|
|
@@ -4099,8 +4407,73 @@ var Orchestrator = class {
|
|
|
4099
4407
|
}
|
|
4100
4408
|
}
|
|
4101
4409
|
}
|
|
4102
|
-
|
|
4103
|
-
|
|
4410
|
+
let lastStderr = summarizeTurnStderr(stderrTail);
|
|
4411
|
+
let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
|
|
4412
|
+
if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && looksLikeInvalidAgentSession(detail) && this.requireThread(threadId).sessionId && this.requireThread(threadId).agent !== "cursor" && this.requireThread(threadId).agent !== "brightsy") {
|
|
4413
|
+
updateThread(threadId, { sessionId: null });
|
|
4414
|
+
pushTurnStderr(
|
|
4415
|
+
stderrTail,
|
|
4416
|
+
"Agent session missing \u2014 starting a fresh session"
|
|
4417
|
+
);
|
|
4418
|
+
this.emit({
|
|
4419
|
+
type: "turn_output",
|
|
4420
|
+
threadId,
|
|
4421
|
+
event: {
|
|
4422
|
+
type: "stderr",
|
|
4423
|
+
data: "Agent session missing \u2014 starting a fresh session"
|
|
4424
|
+
}
|
|
4425
|
+
});
|
|
4426
|
+
const retryThread = this.requireThread(threadId);
|
|
4427
|
+
const prior = retryThread.messages.slice(0, -1);
|
|
4428
|
+
const retrySeed = buildSessionSeed(prior);
|
|
4429
|
+
const retryInstructions = retryThread.agent === "claude" ? null : formatAgentInstructions(
|
|
4430
|
+
loadAgentInstructions(retryThread.worktreePath, retryThread.agent)
|
|
4431
|
+
);
|
|
4432
|
+
const retryPrefix = [
|
|
4433
|
+
coordinatorDirective,
|
|
4434
|
+
worktreeDirective,
|
|
4435
|
+
artifactDirective,
|
|
4436
|
+
renameBranchDirective,
|
|
4437
|
+
retryInstructions,
|
|
4438
|
+
retrySeed
|
|
4439
|
+
].filter(Boolean).join("\n\n---\n\n");
|
|
4440
|
+
const retryHandle = await spawnAgentTurn(
|
|
4441
|
+
retryThread,
|
|
4442
|
+
{ cachedPrefix: retryPrefix, prompt: agentPrompt },
|
|
4443
|
+
(event) => {
|
|
4444
|
+
this.emit({ type: "turn_output", threadId, event });
|
|
4445
|
+
if (event.type === "session_id") {
|
|
4446
|
+
updateThread(threadId, { sessionId: event.data });
|
|
4447
|
+
}
|
|
4448
|
+
if (event.type === "stderr" && typeof event.data === "string") {
|
|
4449
|
+
pushTurnStderr(stderrTail, event.data);
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
4452
|
+
);
|
|
4453
|
+
this.activeTurns.set(threadId, retryHandle);
|
|
4454
|
+
if (typeof retryHandle.pid === "number" && retryHandle.pid > 0) {
|
|
4455
|
+
updateThread(threadId, { agentPid: retryHandle.pid });
|
|
4456
|
+
}
|
|
4457
|
+
this.processes.set(`${threadId}:agent`, {
|
|
4458
|
+
kind: "agent",
|
|
4459
|
+
pid: retryHandle.pid,
|
|
4460
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4461
|
+
kill: retryHandle.kill
|
|
4462
|
+
});
|
|
4463
|
+
if (this.stoppedTurns.has(threadId)) {
|
|
4464
|
+
retryHandle.kill();
|
|
4465
|
+
}
|
|
4466
|
+
const retryResult = await retryHandle.done;
|
|
4467
|
+
if (retryResult.sessionId) {
|
|
4468
|
+
updateThread(threadId, { sessionId: retryResult.sessionId });
|
|
4469
|
+
}
|
|
4470
|
+
assistantText = retryResult.assistantText.trim();
|
|
4471
|
+
parts = retryResult.parts;
|
|
4472
|
+
usage = retryResult.usage ?? void 0;
|
|
4473
|
+
exitCode = retryResult.exitCode;
|
|
4474
|
+
lastStderr = summarizeTurnStderr(stderrTail);
|
|
4475
|
+
detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
|
|
4476
|
+
}
|
|
4104
4477
|
let chatText = assistantText;
|
|
4105
4478
|
if (exitCode !== 0 && !chatText && looksLikeAgentFailureMessage(detail)) {
|
|
4106
4479
|
chatText = humanizeAgentFailDetail(detail);
|
|
@@ -4116,6 +4489,19 @@ var Orchestrator = class {
|
|
|
4116
4489
|
});
|
|
4117
4490
|
}
|
|
4118
4491
|
const afterTurn = this.requireThread(threadId);
|
|
4492
|
+
if (afterTurn.planMode && afterTurn.worktreePath?.trim()) {
|
|
4493
|
+
const presented = extractPresentedPlan(parts);
|
|
4494
|
+
const exited = parts.some(
|
|
4495
|
+
(p) => p.type === "tool" && /exitplanmode/i.test(p.name)
|
|
4496
|
+
);
|
|
4497
|
+
if (presented?.content) {
|
|
4498
|
+
writePlanFile(afterTurn.worktreePath, presented.content);
|
|
4499
|
+
} else if (exited || chatText && chatText.trim().length >= 400) {
|
|
4500
|
+
if (!readPlanFile(afterTurn.worktreePath) && chatText?.trim()) {
|
|
4501
|
+
writePlanFile(afterTurn.worktreePath, chatText.trim());
|
|
4502
|
+
}
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4119
4505
|
if (afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
|
|
4120
4506
|
(p) => p.type === "tool" && /exitplanmode/i.test(p.name)
|
|
4121
4507
|
)) {
|
|
@@ -4597,6 +4983,82 @@ var Orchestrator = class {
|
|
|
4597
4983
|
}
|
|
4598
4984
|
return meta;
|
|
4599
4985
|
}
|
|
4986
|
+
async getPrStack(threadRef) {
|
|
4987
|
+
const thread = this.requireThread(threadRef);
|
|
4988
|
+
if (!thread.worktreePath?.trim()) return null;
|
|
4989
|
+
const stack = await getPrStack(thread.worktreePath);
|
|
4990
|
+
if (!stack) return null;
|
|
4991
|
+
const current = stack.currentIndex >= 0 ? stack.layers[stack.currentIndex] : null;
|
|
4992
|
+
const patch = {};
|
|
4993
|
+
if (stack.stackNumber != null) {
|
|
4994
|
+
const id = `gh-stack-${stack.stackNumber}`;
|
|
4995
|
+
if (thread.stackId !== id) patch.stackId = id;
|
|
4996
|
+
}
|
|
4997
|
+
if (current?.position != null && thread.stackLayer !== current.position) {
|
|
4998
|
+
patch.stackLayer = current.position;
|
|
4999
|
+
}
|
|
5000
|
+
if (current?.prUrl && current.prUrl !== thread.prUrl) patch.prUrl = current.prUrl;
|
|
5001
|
+
if (current?.title && current.title !== thread.prTitle) patch.prTitle = current.title;
|
|
5002
|
+
if (current?.branchName && current.branchName !== thread.branchName) {
|
|
5003
|
+
patch.branchName = current.branchName;
|
|
5004
|
+
}
|
|
5005
|
+
if (Object.keys(patch).length > 0) updateThread(thread.id, patch);
|
|
5006
|
+
return stack;
|
|
5007
|
+
}
|
|
5008
|
+
/** Open worktrees for all (or one) stack layers discovered from a thread. */
|
|
5009
|
+
async openPrStackLayers(threadRef, opts) {
|
|
5010
|
+
const result = await openPrStackLayers({ threadRef, layer: opts?.layer });
|
|
5011
|
+
for (const t of result.threads) {
|
|
5012
|
+
this.emit({ type: "status_changed", threadId: t.id, status: t.status });
|
|
5013
|
+
}
|
|
5014
|
+
for (const id of result.createdThreadIds) {
|
|
5015
|
+
await this.runSetupAfterCreate(id);
|
|
5016
|
+
}
|
|
5017
|
+
return { stack: result.stack, threads: result.threads };
|
|
5018
|
+
}
|
|
5019
|
+
/** Add a branch on top of the thread's stack and open its worktree. */
|
|
5020
|
+
async addStackLayer(threadRef, branchName, opts) {
|
|
5021
|
+
const result = await addStackLayerFromThread({
|
|
5022
|
+
threadRef,
|
|
5023
|
+
branchName,
|
|
5024
|
+
title: opts?.title
|
|
5025
|
+
});
|
|
5026
|
+
this.emit({
|
|
5027
|
+
type: "status_changed",
|
|
5028
|
+
threadId: result.thread.id,
|
|
5029
|
+
status: result.thread.status
|
|
5030
|
+
});
|
|
5031
|
+
if (result.createdWorktree) {
|
|
5032
|
+
await this.runSetupAfterCreate(result.thread.id);
|
|
5033
|
+
}
|
|
5034
|
+
return { stack: result.stack, thread: result.thread };
|
|
5035
|
+
}
|
|
5036
|
+
/** Initialize a stack from the current thread branch (optional extra layers). */
|
|
5037
|
+
async initStackFromThread(threadRef, opts) {
|
|
5038
|
+
const result = await initStackFromThread({
|
|
5039
|
+
threadRef,
|
|
5040
|
+
additionalBranches: opts?.additionalBranches,
|
|
5041
|
+
base: opts?.base
|
|
5042
|
+
});
|
|
5043
|
+
for (const t of result.threads) {
|
|
5044
|
+
this.emit({ type: "status_changed", threadId: t.id, status: t.status });
|
|
5045
|
+
}
|
|
5046
|
+
for (const id of result.createdThreadIds) {
|
|
5047
|
+
await this.runSetupAfterCreate(id);
|
|
5048
|
+
}
|
|
5049
|
+
return { stack: result.stack, threads: result.threads };
|
|
5050
|
+
}
|
|
5051
|
+
/** Create a new multi-layer stack with one worktree per layer. */
|
|
5052
|
+
async createPrStack(input) {
|
|
5053
|
+
const result = await createPrStack(input);
|
|
5054
|
+
for (const t of result.threads) {
|
|
5055
|
+
this.emit({ type: "status_changed", threadId: t.id, status: t.status });
|
|
5056
|
+
}
|
|
5057
|
+
for (const id of result.createdThreadIds) {
|
|
5058
|
+
await this.runSetupAfterCreate(id);
|
|
5059
|
+
}
|
|
5060
|
+
return { stack: result.stack, threads: result.threads };
|
|
5061
|
+
}
|
|
4600
5062
|
async getPrDetails(threadRef) {
|
|
4601
5063
|
const { thread, selector, cwd } = await this.withPrSelector(threadRef);
|
|
4602
5064
|
if (!selector) return null;
|
|
@@ -4659,14 +5121,9 @@ var Orchestrator = class {
|
|
|
4659
5121
|
return forkChatTab(input);
|
|
4660
5122
|
}
|
|
4661
5123
|
async forkThreadWorktree(input) {
|
|
4662
|
-
const thread = await forkThreadWorktree(input
|
|
4663
|
-
this.emit({
|
|
4664
|
-
type: "turn_output",
|
|
4665
|
-
threadId: "pending",
|
|
4666
|
-
event: { type: "stdout", data: line }
|
|
4667
|
-
});
|
|
4668
|
-
});
|
|
5124
|
+
const thread = await forkThreadWorktree(input);
|
|
4669
5125
|
this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
|
|
5126
|
+
await this.runSetupAfterCreate(thread.id);
|
|
4670
5127
|
return thread;
|
|
4671
5128
|
}
|
|
4672
5129
|
renameThread(threadRef, title) {
|
|
@@ -4680,7 +5137,7 @@ var Orchestrator = class {
|
|
|
4680
5137
|
}
|
|
4681
5138
|
/**
|
|
4682
5139
|
* Stage OS / worktree files into composer attachments (copies external files
|
|
4683
|
-
* into `.
|
|
5140
|
+
* into `.context/attachments/` so agents can Read images and binaries).
|
|
4684
5141
|
*/
|
|
4685
5142
|
attachComposerFiles(threadRef, opts) {
|
|
4686
5143
|
const thread = this.requireThread(threadRef);
|
|
@@ -4754,8 +5211,8 @@ var Orchestrator = class {
|
|
|
4754
5211
|
updateThread(thread.id, { worktreePath: globalAgentCwd() });
|
|
4755
5212
|
return setStatus(thread.id, "idle");
|
|
4756
5213
|
}
|
|
4757
|
-
if (!
|
|
4758
|
-
const { createThreadWorktree: createThreadWorktree2 } = await import("../worktree-
|
|
5214
|
+
if (!existsSync13(thread.worktreePath)) {
|
|
5215
|
+
const { createThreadWorktree: createThreadWorktree2 } = await import("../worktree-5KEQWSAF.js");
|
|
4759
5216
|
const { execa: execa6 } = await import("execa");
|
|
4760
5217
|
const slug = thread.worktreePath.split("/").pop();
|
|
4761
5218
|
const dest = thread.worktreePath;
|
|
@@ -5021,6 +5478,59 @@ async function startMcpServer() {
|
|
|
5021
5478
|
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
5022
5479
|
}
|
|
5023
5480
|
);
|
|
5481
|
+
server.tool(
|
|
5482
|
+
"ask_user",
|
|
5483
|
+
"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.",
|
|
5484
|
+
{
|
|
5485
|
+
questions: z.array(
|
|
5486
|
+
z.object({
|
|
5487
|
+
question: z.string().describe("Full question text ending with ?"),
|
|
5488
|
+
header: z.string().max(24).optional().describe("Short label shown above the question"),
|
|
5489
|
+
multiSelect: z.boolean().optional().describe("Allow selecting multiple options"),
|
|
5490
|
+
options: z.array(
|
|
5491
|
+
z.object({
|
|
5492
|
+
label: z.string(),
|
|
5493
|
+
description: z.string().optional().describe("What this option means / when to choose it (strongly preferred)")
|
|
5494
|
+
})
|
|
5495
|
+
).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
|
|
5496
|
+
})
|
|
5497
|
+
).min(1).max(4).describe("1\u20134 questions")
|
|
5498
|
+
},
|
|
5499
|
+
async ({ questions }) => {
|
|
5500
|
+
const payload = {
|
|
5501
|
+
ok: true,
|
|
5502
|
+
questions,
|
|
5503
|
+
message: "Questions shown in Sideboard\u2019s composer. Wait for the user\u2019s next message with their answers before continuing."
|
|
5504
|
+
};
|
|
5505
|
+
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
5506
|
+
}
|
|
5507
|
+
);
|
|
5508
|
+
server.tool(
|
|
5509
|
+
"present_plan",
|
|
5510
|
+
"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.",
|
|
5511
|
+
{
|
|
5512
|
+
title: z.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
5513
|
+
content: z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
5514
|
+
thread_id: z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
5515
|
+
},
|
|
5516
|
+
async ({ title, content, thread_id }) => {
|
|
5517
|
+
const { writePlanFile: writePlanFile2 } = await import("../plan-file-PHVKUAEE.js");
|
|
5518
|
+
let root = process.cwd();
|
|
5519
|
+
if (thread_id?.trim()) {
|
|
5520
|
+
const t = orch.getThread(thread_id.trim());
|
|
5521
|
+
if (t?.worktreePath?.trim()) root = t.worktreePath;
|
|
5522
|
+
}
|
|
5523
|
+
const path = writePlanFile2(root, content);
|
|
5524
|
+
const payload = {
|
|
5525
|
+
ok: true,
|
|
5526
|
+
path,
|
|
5527
|
+
title: title?.trim() || "Plan",
|
|
5528
|
+
content,
|
|
5529
|
+
message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
|
|
5530
|
+
};
|
|
5531
|
+
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
5532
|
+
}
|
|
5533
|
+
);
|
|
5024
5534
|
server.tool(
|
|
5025
5535
|
"present_schema",
|
|
5026
5536
|
"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.",
|
|
@@ -5599,6 +6109,126 @@ async function startMcpServer() {
|
|
|
5599
6109
|
};
|
|
5600
6110
|
}
|
|
5601
6111
|
);
|
|
6112
|
+
server.tool(
|
|
6113
|
+
"get_pr_stack",
|
|
6114
|
+
"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.",
|
|
6115
|
+
{ ref: z.string() },
|
|
6116
|
+
async ({ ref }) => {
|
|
6117
|
+
const stack = await orch.getPrStack(ref);
|
|
6118
|
+
return {
|
|
6119
|
+
content: [{ type: "text", text: JSON.stringify(stack, null, 2) }]
|
|
6120
|
+
};
|
|
6121
|
+
}
|
|
6122
|
+
);
|
|
6123
|
+
server.tool(
|
|
6124
|
+
"open_pr_stack_layers",
|
|
6125
|
+
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
6126
|
+
{
|
|
6127
|
+
ref: z.string(),
|
|
6128
|
+
layer: z.number().int().positive().optional()
|
|
6129
|
+
},
|
|
6130
|
+
async ({ ref, layer }) => {
|
|
6131
|
+
const result = await orch.openPrStackLayers(ref, { layer });
|
|
6132
|
+
return {
|
|
6133
|
+
content: [
|
|
6134
|
+
{
|
|
6135
|
+
type: "text",
|
|
6136
|
+
text: JSON.stringify(
|
|
6137
|
+
{
|
|
6138
|
+
stackNumber: result.stack.stackNumber,
|
|
6139
|
+
trunk: result.stack.trunk,
|
|
6140
|
+
threads: result.threads.map((t) => ({
|
|
6141
|
+
id: t.id,
|
|
6142
|
+
title: t.title,
|
|
6143
|
+
branchName: t.branchName,
|
|
6144
|
+
stackLayer: t.stackLayer,
|
|
6145
|
+
worktreePath: t.worktreePath,
|
|
6146
|
+
prUrl: t.prUrl,
|
|
6147
|
+
link: `sideboard://thread/${t.id}`
|
|
6148
|
+
}))
|
|
6149
|
+
},
|
|
6150
|
+
null,
|
|
6151
|
+
2
|
|
6152
|
+
)
|
|
6153
|
+
}
|
|
6154
|
+
]
|
|
6155
|
+
};
|
|
6156
|
+
}
|
|
6157
|
+
);
|
|
6158
|
+
server.tool(
|
|
6159
|
+
"add_stack_layer",
|
|
6160
|
+
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
6161
|
+
{
|
|
6162
|
+
ref: z.string(),
|
|
6163
|
+
branchName: z.string(),
|
|
6164
|
+
title: z.string().optional()
|
|
6165
|
+
},
|
|
6166
|
+
async ({ ref, branchName, title }) => {
|
|
6167
|
+
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
6168
|
+
return {
|
|
6169
|
+
content: [
|
|
6170
|
+
{
|
|
6171
|
+
type: "text",
|
|
6172
|
+
text: JSON.stringify(
|
|
6173
|
+
{
|
|
6174
|
+
id: result.thread.id,
|
|
6175
|
+
title: result.thread.title,
|
|
6176
|
+
branchName: result.thread.branchName,
|
|
6177
|
+
stackLayer: result.thread.stackLayer,
|
|
6178
|
+
worktreePath: result.thread.worktreePath,
|
|
6179
|
+
link: `sideboard://thread/${result.thread.id}`
|
|
6180
|
+
},
|
|
6181
|
+
null,
|
|
6182
|
+
2
|
|
6183
|
+
)
|
|
6184
|
+
}
|
|
6185
|
+
]
|
|
6186
|
+
};
|
|
6187
|
+
}
|
|
6188
|
+
);
|
|
6189
|
+
server.tool(
|
|
6190
|
+
"create_pr_stack",
|
|
6191
|
+
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
6192
|
+
{
|
|
6193
|
+
repoPath: z.string(),
|
|
6194
|
+
branches: z.array(z.string()).min(1),
|
|
6195
|
+
agent: z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
|
|
6196
|
+
base: z.string().optional(),
|
|
6197
|
+
title: z.string().optional()
|
|
6198
|
+
},
|
|
6199
|
+
async (args) => {
|
|
6200
|
+
const result = await orch.createPrStack({
|
|
6201
|
+
repoPath: args.repoPath,
|
|
6202
|
+
branches: args.branches,
|
|
6203
|
+
agent: args.agent,
|
|
6204
|
+
base: args.base,
|
|
6205
|
+
title: args.title
|
|
6206
|
+
});
|
|
6207
|
+
return {
|
|
6208
|
+
content: [
|
|
6209
|
+
{
|
|
6210
|
+
type: "text",
|
|
6211
|
+
text: JSON.stringify(
|
|
6212
|
+
{
|
|
6213
|
+
stackNumber: result.stack.stackNumber,
|
|
6214
|
+
trunk: result.stack.trunk,
|
|
6215
|
+
threads: result.threads.map((t) => ({
|
|
6216
|
+
id: t.id,
|
|
6217
|
+
title: t.title,
|
|
6218
|
+
branchName: t.branchName,
|
|
6219
|
+
stackLayer: t.stackLayer,
|
|
6220
|
+
worktreePath: t.worktreePath,
|
|
6221
|
+
link: `sideboard://thread/${t.id}`
|
|
6222
|
+
}))
|
|
6223
|
+
},
|
|
6224
|
+
null,
|
|
6225
|
+
2
|
|
6226
|
+
)
|
|
6227
|
+
}
|
|
6228
|
+
]
|
|
6229
|
+
};
|
|
6230
|
+
}
|
|
6231
|
+
);
|
|
5602
6232
|
server.tool(
|
|
5603
6233
|
"list_issues",
|
|
5604
6234
|
"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.",
|