@sideboard-ai/core 0.1.61 → 0.1.66
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-B2723EOF.js → agents-EWFD765A.js} +23 -7
- package/dist/{agents-EADRAAPG.js → agents-MD7SCFYX.js} +23 -7
- package/dist/{app-settings-YBNZ4XSR.js → app-settings-GJVZGU3M.js} +15 -3
- package/dist/{app-settings-TBHTJWLQ.js → app-settings-ONKMFSEJ.js} +15 -3
- package/dist/{chunk-RIWUU7FO.js → chunk-5AG5BUQN.js} +61 -3
- package/dist/{chunk-HLEX5AQ6.js → chunk-7FD7COKE.js} +4 -0
- package/dist/{chunk-XWPKLRB3.js → chunk-AKPEXIKK.js} +2 -2
- package/dist/{chunk-MX7UH5AT.js → chunk-BBEH2CJE.js} +264 -93
- package/dist/{chunk-FGNBTFRD.js → chunk-BPOTVI5J.js} +125 -3
- package/dist/{chunk-CHGOMFTA.js → chunk-DNPP7QMK.js} +32 -7
- package/dist/{chunk-MBAL5FE6.js → chunk-DZYMNW2A.js} +4 -4
- package/dist/{chunk-JB2H7E7V.js → chunk-EVD4KNKR.js} +1 -1
- package/dist/{chunk-TSRXOSVD.js → chunk-FWJYLBO6.js} +4 -0
- package/dist/{chunk-QJBC4BRK.js → chunk-HTOTTJJX.js} +2 -2
- package/dist/{chunk-MFGGIRUA.js → chunk-I2KGRAXC.js} +2 -2
- package/dist/{chunk-ZXDOI6IH.js → chunk-IFAU2DEY.js} +269 -93
- package/dist/{chunk-NTYRBRE7.js → chunk-LRBRQFBU.js} +2 -2
- package/dist/{chunk-75QR74KM.js → chunk-MHR4OGZ4.js} +125 -3
- package/dist/{chunk-2P2UK7E7.js → chunk-PESJDZTB.js} +2 -2
- package/dist/{chunk-P7EQUFB6.js → chunk-R7GUDEPY.js} +2 -2
- package/dist/{chunk-73AT5K4A.js → chunk-TXQ6K2GR.js} +61 -3
- package/dist/{chunk-MLAST5BB.js → chunk-YTNLOJPK.js} +32 -7
- package/dist/{connected-teams-RSL4VB3X.js → connected-teams-CHXO5FFA.js} +2 -2
- package/dist/{connected-teams-MKIYKOYA.js → connected-teams-GJTESSTX.js} +2 -2
- package/dist/{coordinator-prompt-4P23VVFC.js → coordinator-prompt-E5UGSJES.js} +5 -5
- package/dist/{coordinator-prompt-LNWHZ64D.js → coordinator-prompt-ILSULI7C.js} +5 -5
- package/dist/{global-workspace-TMMAVRVW.js → global-workspace-44UG64NA.js} +6 -7
- package/dist/{global-workspace-BHT4XUAA.js → global-workspace-5PM5LA6E.js} +6 -7
- package/dist/index.cjs +1433 -944
- package/dist/index.d.cts +117 -2
- package/dist/index.d.ts +117 -2
- package/dist/index.js +859 -736
- package/dist/mcp/run-stdio.cjs +1388 -993
- package/dist/mcp/run-stdio.js +798 -765
- package/dist/{run-HZTBWGQR.js → run-74S2NZWB.js} +1 -1
- package/dist/{run-GPSVV5OH.js → run-JLN5JK7Q.js} +1 -1
- package/dist/{thread-store-GHOADGL2.js → thread-store-BUBR24YA.js} +1 -1
- package/dist/{thread-store-UJIGMI5J.js → thread-store-XTPFCJAI.js} +1 -1
- package/dist/{workspaces-3GO3EDQG.js → workspaces-OVBCSS5Y.js} +7 -8
- package/dist/{workspaces-GFPGDGCS.js → workspaces-RXFXQRRF.js} +7 -8
- package/dist/{worktree-GBLHEFHR.js → worktree-K2JB53R5.js} +3 -3
- package/dist/{worktree-3L2G6PCM.js → worktree-S2N63FLV.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-GI7E5733.js +0 -30
- package/dist/chunk-S2LL5HA4.js +0 -33
package/dist/mcp/run-stdio.js
CHANGED
|
@@ -14,15 +14,16 @@ import {
|
|
|
14
14
|
parseSessionQuotaResetAt,
|
|
15
15
|
pushTurnStderr,
|
|
16
16
|
resolveQuotaFallbackAgent,
|
|
17
|
+
sideboardMcpProfile,
|
|
17
18
|
summarizeTurnStderr
|
|
18
|
-
} from "../chunk-
|
|
19
|
-
import "../chunk-
|
|
19
|
+
} from "../chunk-BBEH2CJE.js";
|
|
20
|
+
import "../chunk-EVD4KNKR.js";
|
|
20
21
|
import {
|
|
21
22
|
addWorkspace,
|
|
22
23
|
ensureWorkspace,
|
|
23
24
|
removeWorkspace,
|
|
24
25
|
syncWorkspacesFromThreads
|
|
25
|
-
} from "../chunk-
|
|
26
|
+
} from "../chunk-HTOTTJJX.js";
|
|
26
27
|
import {
|
|
27
28
|
extractPresentedPlan,
|
|
28
29
|
readPlanFile,
|
|
@@ -30,22 +31,20 @@ import {
|
|
|
30
31
|
} from "../chunk-N5PM7HGQ.js";
|
|
31
32
|
import {
|
|
32
33
|
GLOBAL_WORKSPACE_ID,
|
|
34
|
+
assertOrchestratorCapableAgent,
|
|
33
35
|
healOrchestrationSoccerTitles,
|
|
34
36
|
isCloudCoordinatorThread,
|
|
35
37
|
isGlobalRepoPath,
|
|
36
38
|
isGlobalThread,
|
|
37
39
|
isOrchestratorThread,
|
|
38
40
|
orchestratorSessionPoisonedByBuiltins
|
|
39
|
-
} from "../chunk-
|
|
40
|
-
import {
|
|
41
|
-
assertOrchestratorCapableAgent
|
|
42
|
-
} from "../chunk-S2LL5HA4.js";
|
|
41
|
+
} from "../chunk-DNPP7QMK.js";
|
|
43
42
|
import {
|
|
44
43
|
coordinatorSystemPrompt,
|
|
45
44
|
coordinatorTurnReminder,
|
|
46
45
|
enrichWorkspacesWithGithub,
|
|
47
46
|
ensureGlobalCoordinatorCwd
|
|
48
|
-
} from "../chunk-
|
|
47
|
+
} from "../chunk-LRBRQFBU.js";
|
|
49
48
|
import {
|
|
50
49
|
addPrStackLayer,
|
|
51
50
|
allocateTeamName,
|
|
@@ -82,7 +81,7 @@ import {
|
|
|
82
81
|
takenSlugsFromThread,
|
|
83
82
|
threadDisplayLabel,
|
|
84
83
|
worktreeNameFromPath
|
|
85
|
-
} from "../chunk-
|
|
84
|
+
} from "../chunk-I2KGRAXC.js";
|
|
86
85
|
import {
|
|
87
86
|
ATTACHMENTS_DIR,
|
|
88
87
|
LEGACY_ATTACHMENTS_DIR,
|
|
@@ -99,13 +98,14 @@ import {
|
|
|
99
98
|
updateThread,
|
|
100
99
|
withThreadLock,
|
|
101
100
|
writeThread
|
|
102
|
-
} from "../chunk-
|
|
101
|
+
} from "../chunk-7FD7COKE.js";
|
|
103
102
|
import {
|
|
103
|
+
enrichPathWithNpmGlobalBin,
|
|
104
104
|
ensureAgentPath,
|
|
105
105
|
gh,
|
|
106
106
|
git,
|
|
107
107
|
run
|
|
108
|
-
} from "../chunk-
|
|
108
|
+
} from "../chunk-5AG5BUQN.js";
|
|
109
109
|
import {
|
|
110
110
|
childEnvWithAppSettings,
|
|
111
111
|
getLinearApiKey,
|
|
@@ -116,7 +116,7 @@ import {
|
|
|
116
116
|
resolveEffectiveIssueSource,
|
|
117
117
|
resolveNewThreadOptions,
|
|
118
118
|
updateAdvancedSettings
|
|
119
|
-
} from "../chunk-
|
|
119
|
+
} from "../chunk-BPOTVI5J.js";
|
|
120
120
|
import "../chunk-KGZBYWZ3.js";
|
|
121
121
|
import {
|
|
122
122
|
loadRepoSettings,
|
|
@@ -391,9 +391,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
391
391
|
`Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
|
|
392
392
|
);
|
|
393
393
|
}
|
|
394
|
-
const { isGlobalThread: isGlobalThread2 } = await import("../global-workspace-
|
|
394
|
+
const { isGlobalThread: isGlobalThread2 } = await import("../global-workspace-5PM5LA6E.js");
|
|
395
395
|
if (isGlobalThread2(thread)) {
|
|
396
|
-
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("../coordinator-prompt-
|
|
396
|
+
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("../coordinator-prompt-E5UGSJES.js");
|
|
397
397
|
ensureGlobalCoordinatorCwd2(
|
|
398
398
|
isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
|
|
399
399
|
);
|
|
@@ -495,6 +495,20 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
495
495
|
};
|
|
496
496
|
}
|
|
497
497
|
|
|
498
|
+
// src/git/pr-merge-archive.ts
|
|
499
|
+
function normalizePrState(state) {
|
|
500
|
+
return (state ?? "").trim().toUpperCase();
|
|
501
|
+
}
|
|
502
|
+
function shouldAutoArchiveOnPrMerge(opts) {
|
|
503
|
+
if (!opts.autoArchiveEnabled) return false;
|
|
504
|
+
if (opts.isGlobal) return false;
|
|
505
|
+
if (opts.skipAutoArchiveOnMerge) return false;
|
|
506
|
+
if (opts.threadStatus === "archived") return false;
|
|
507
|
+
const next = normalizePrState(opts.nextPrState);
|
|
508
|
+
if (next !== "MERGED") return false;
|
|
509
|
+
return normalizePrState(opts.previousPrState) !== "MERGED";
|
|
510
|
+
}
|
|
511
|
+
|
|
498
512
|
// src/hook/conductor.ts
|
|
499
513
|
import {
|
|
500
514
|
copyFileSync,
|
|
@@ -1103,6 +1117,7 @@ import { existsSync as existsSync5 } from "fs";
|
|
|
1103
1117
|
var REQUIRE_AGENT_TIMEOUT_MS = 8e3;
|
|
1104
1118
|
async function requireAgent(agent, opts) {
|
|
1105
1119
|
ensureAgentPath();
|
|
1120
|
+
enrichPathWithNpmGlobalBin();
|
|
1106
1121
|
const status = await Promise.race([
|
|
1107
1122
|
getAdapter(agent).detect(),
|
|
1108
1123
|
new Promise((_, reject) => {
|
|
@@ -1194,7 +1209,7 @@ async function createThread(input, _onSetupLine) {
|
|
|
1194
1209
|
return readThread(thread.id) ?? thread;
|
|
1195
1210
|
}
|
|
1196
1211
|
async function listLinearIssues(agent, repoPath) {
|
|
1197
|
-
const { getAdapter: getAdapter2 } = await import("../agents-
|
|
1212
|
+
const { getAdapter: getAdapter2 } = await import("../agents-MD7SCFYX.js");
|
|
1198
1213
|
await requireAgent(agent, { requireLinear: true });
|
|
1199
1214
|
const adapter = getAdapter2(agent);
|
|
1200
1215
|
if (!adapter.listLinearIssues) {
|
|
@@ -1512,6 +1527,8 @@ function worktreeBindingFrom(from) {
|
|
|
1512
1527
|
parentThreadId: from.parentThreadId,
|
|
1513
1528
|
prUrl: from.prUrl,
|
|
1514
1529
|
prTitle: from.prTitle,
|
|
1530
|
+
prState: from.prState,
|
|
1531
|
+
skipAutoArchiveOnMerge: from.skipAutoArchiveOnMerge,
|
|
1515
1532
|
stackId: from.stackId,
|
|
1516
1533
|
stackLayer: from.stackLayer
|
|
1517
1534
|
};
|
|
@@ -2041,7 +2058,7 @@ async function adoptThread(input) {
|
|
|
2041
2058
|
throw new Error(`Worktree not found: ${input.worktreePath}`);
|
|
2042
2059
|
}
|
|
2043
2060
|
const repoPath = await resolveRepoRoot(input.worktreePath);
|
|
2044
|
-
const { stdout: branch } = await import("../run-
|
|
2061
|
+
const { stdout: branch } = await import("../run-74S2NZWB.js").then(
|
|
2045
2062
|
({ git: git2 }) => git2(["rev-parse", "--abbrev-ref", "HEAD"], input.worktreePath)
|
|
2046
2063
|
);
|
|
2047
2064
|
const thread = createEmptyThread({
|
|
@@ -2056,7 +2073,7 @@ async function adoptThread(input) {
|
|
|
2056
2073
|
messages: input.messages ?? []
|
|
2057
2074
|
});
|
|
2058
2075
|
writeThread(thread);
|
|
2059
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-
|
|
2076
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-RXFXQRRF.js");
|
|
2060
2077
|
await ensureWorkspace2(repoPath);
|
|
2061
2078
|
return thread;
|
|
2062
2079
|
}
|
|
@@ -3162,7 +3179,7 @@ async function previewLand(thread) {
|
|
|
3162
3179
|
const target = await resolveDefaultBranch(thread.repoPath);
|
|
3163
3180
|
if (thread.branchName === target || thread.branchName === "main" || thread.branchName === "master") {
|
|
3164
3181
|
}
|
|
3165
|
-
const { stdout: head } = await import("../run-
|
|
3182
|
+
const { stdout: head } = await import("../run-74S2NZWB.js").then(
|
|
3166
3183
|
({ git: git2 }) => git2(["rev-parse", "--abbrev-ref", "HEAD"], thread.worktreePath)
|
|
3167
3184
|
);
|
|
3168
3185
|
const current = head.trim();
|
|
@@ -3201,7 +3218,7 @@ async function confirmLand(thread, opts) {
|
|
|
3201
3218
|
if (preview.dirty) {
|
|
3202
3219
|
committed = await commitAll(thread.worktreePath, meta.commitMessage);
|
|
3203
3220
|
}
|
|
3204
|
-
const { git: git2 } = await import("../run-
|
|
3221
|
+
const { git: git2 } = await import("../run-74S2NZWB.js");
|
|
3205
3222
|
const { stdout: headOut } = await git2(
|
|
3206
3223
|
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
3207
3224
|
thread.worktreePath,
|
|
@@ -3738,6 +3755,9 @@ function formatWorktreeDirective(thread, opts) {
|
|
|
3738
3755
|
);
|
|
3739
3756
|
return lines.join("\n");
|
|
3740
3757
|
}
|
|
3758
|
+
function formatWorktreeReminder() {
|
|
3759
|
+
return "Sideboard worktree: stay in this cwd for all file and git work. Push and open PRs against origin, never upstream. Do not edit the main repo checkout.";
|
|
3760
|
+
}
|
|
3741
3761
|
function formatArtifactDirective() {
|
|
3742
3762
|
return [
|
|
3743
3763
|
"Sideboard side column (desktop UI):",
|
|
@@ -3758,60 +3778,6 @@ function formatArtifactDirective() {
|
|
|
3758
3778
|
"Never say artifacts, CMS UI, or the Files column are unavailable. Prefer present_schema for list/edit/publish; present_files for storage UI; html fences for standalone pages."
|
|
3759
3779
|
].join("\n");
|
|
3760
3780
|
}
|
|
3761
|
-
var FILES_BY_AGENT = {
|
|
3762
|
-
claude: [
|
|
3763
|
-
"CLAUDE.md",
|
|
3764
|
-
".claude/CLAUDE.md",
|
|
3765
|
-
"CLAUDE.local.md",
|
|
3766
|
-
"AGENTS.md",
|
|
3767
|
-
"AGENT.md"
|
|
3768
|
-
],
|
|
3769
|
-
codex: ["AGENTS.md", "AGENT.md", ".codex/AGENTS.md", "CLAUDE.md"],
|
|
3770
|
-
opencode: ["AGENTS.md", "AGENT.md", "OPENCODE.md", "CLAUDE.md"],
|
|
3771
|
-
brightsy: ["AGENTS.md", "AGENT.md", "CLAUDE.md"],
|
|
3772
|
-
cursor: ["AGENTS.md", "AGENT.md", ".cursor/rules", "CLAUDE.md"]
|
|
3773
|
-
};
|
|
3774
|
-
var MAX_CHARS_PER_FILE = 48e3;
|
|
3775
|
-
function loadAgentInstructions(worktreePath, agent) {
|
|
3776
|
-
const candidates = FILES_BY_AGENT[agent] ?? FILES_BY_AGENT.claude;
|
|
3777
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3778
|
-
const out = [];
|
|
3779
|
-
for (const rel of candidates) {
|
|
3780
|
-
if (seen.has(rel)) continue;
|
|
3781
|
-
const abs = join10(worktreePath, rel);
|
|
3782
|
-
if (!existsSync12(abs)) continue;
|
|
3783
|
-
try {
|
|
3784
|
-
if (!statSync5(abs).isFile()) continue;
|
|
3785
|
-
let content = readFileSync8(abs, "utf8");
|
|
3786
|
-
if (!content.trim()) continue;
|
|
3787
|
-
if (content.length > MAX_CHARS_PER_FILE) {
|
|
3788
|
-
content = `${content.slice(0, MAX_CHARS_PER_FILE)}
|
|
3789
|
-
|
|
3790
|
-
\u2026(truncated)`;
|
|
3791
|
-
}
|
|
3792
|
-
seen.add(rel);
|
|
3793
|
-
out.push({ relativePath: rel, content });
|
|
3794
|
-
} catch {
|
|
3795
|
-
}
|
|
3796
|
-
}
|
|
3797
|
-
return out;
|
|
3798
|
-
}
|
|
3799
|
-
function formatAgentInstructions(files) {
|
|
3800
|
-
if (files.length === 0) return null;
|
|
3801
|
-
const parts = [
|
|
3802
|
-
"Project agent instructions (from the worktree \u2014 follow these):",
|
|
3803
|
-
""
|
|
3804
|
-
];
|
|
3805
|
-
for (const f of files) {
|
|
3806
|
-
parts.push(`## ${f.relativePath}`);
|
|
3807
|
-
parts.push("");
|
|
3808
|
-
parts.push(f.content.trim());
|
|
3809
|
-
parts.push("");
|
|
3810
|
-
parts.push("---");
|
|
3811
|
-
parts.push("");
|
|
3812
|
-
}
|
|
3813
|
-
return parts.join("\n").trimEnd();
|
|
3814
|
-
}
|
|
3815
3781
|
|
|
3816
3782
|
// src/threads/sync-branch.ts
|
|
3817
3783
|
async function syncThreadBranchFromGit(threadId) {
|
|
@@ -3944,7 +3910,7 @@ var Orchestrator = class {
|
|
|
3944
3910
|
orphans: orphans.map((o) => ({ path: o.path, repoPath: o.repoPath }))
|
|
3945
3911
|
});
|
|
3946
3912
|
}
|
|
3947
|
-
const { autoCleanupOrphansEnabled } = await import("../app-settings-
|
|
3913
|
+
const { autoCleanupOrphansEnabled } = await import("../app-settings-GJVZGU3M.js");
|
|
3948
3914
|
if (autoCleanupOrphansEnabled() && shouldRunWorktreeCleanup() && orphans.length > 0) {
|
|
3949
3915
|
await cleanupOrphanWorktrees({ repoPaths });
|
|
3950
3916
|
}
|
|
@@ -4101,7 +4067,7 @@ var Orchestrator = class {
|
|
|
4101
4067
|
}
|
|
4102
4068
|
async finishCreateThread(threadId, prompt) {
|
|
4103
4069
|
await this.runSetupAfterCreate(threadId);
|
|
4104
|
-
const { autoRunAfterSetupEnabled } = await import("../app-settings-
|
|
4070
|
+
const { autoRunAfterSetupEnabled } = await import("../app-settings-GJVZGU3M.js");
|
|
4105
4071
|
if (autoRunAfterSetupEnabled()) {
|
|
4106
4072
|
try {
|
|
4107
4073
|
await this.startDev(threadId);
|
|
@@ -4342,13 +4308,14 @@ var Orchestrator = class {
|
|
|
4342
4308
|
"Files column: MCP present_files (brightsy account storage or memory).",
|
|
4343
4309
|
"Do not say artifacts/CMS UI are unavailable."
|
|
4344
4310
|
].join(" ") : null;
|
|
4311
|
+
const worktreeReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatWorktreeReminder() : null;
|
|
4345
4312
|
const agentPrompt = [
|
|
4346
4313
|
thread.planMode ? PLAN_MODE_INSTRUCTION : null,
|
|
4347
4314
|
orchestrationReminder,
|
|
4315
|
+
worktreeReminder,
|
|
4348
4316
|
artifactReminder,
|
|
4349
4317
|
expandedPrompt
|
|
4350
4318
|
].filter(Boolean).join("\n\n");
|
|
4351
|
-
const instructionFiles = loadAgentInstructions(thread.worktreePath, thread.agent);
|
|
4352
4319
|
if (thread.attachments.length > 0) {
|
|
4353
4320
|
updateThread(threadId, { attachments: [] });
|
|
4354
4321
|
}
|
|
@@ -4372,11 +4339,10 @@ var Orchestrator = class {
|
|
|
4372
4339
|
});
|
|
4373
4340
|
const artifactDirective = isBrightsy ? null : formatArtifactDirective();
|
|
4374
4341
|
const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
|
|
4375
|
-
const { autoRenameBranchEnabled } = await import("../app-settings-
|
|
4342
|
+
const { autoRenameBranchEnabled } = await import("../app-settings-GJVZGU3M.js");
|
|
4376
4343
|
const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled() ? formatRenameBranchDirective(fresh, {
|
|
4377
4344
|
customPrompt: settings?.prompts?.renameBranch
|
|
4378
4345
|
}) : null;
|
|
4379
|
-
const instructions = fresh.agent === "claude" || isBrightsy ? null : formatAgentInstructions(instructionFiles);
|
|
4380
4346
|
let seed = null;
|
|
4381
4347
|
if (!fresh.sessionId) {
|
|
4382
4348
|
const prior = fresh.messages.slice(0, -1);
|
|
@@ -4397,12 +4363,12 @@ var Orchestrator = class {
|
|
|
4397
4363
|
});
|
|
4398
4364
|
}
|
|
4399
4365
|
}
|
|
4400
|
-
const cachedPrefix = [
|
|
4366
|
+
const cachedPrefix = fresh.sessionId ? "" : [
|
|
4401
4367
|
coordinatorDirective,
|
|
4402
4368
|
worktreeDirective,
|
|
4403
4369
|
artifactDirective,
|
|
4404
4370
|
renameBranchDirective,
|
|
4405
|
-
|
|
4371
|
+
seed
|
|
4406
4372
|
].filter(Boolean).join("\n\n---\n\n");
|
|
4407
4373
|
try {
|
|
4408
4374
|
const stderrTail = [];
|
|
@@ -4489,15 +4455,11 @@ var Orchestrator = class {
|
|
|
4489
4455
|
const retryThread = this.requireThread(threadId);
|
|
4490
4456
|
const prior = retryThread.messages.slice(0, -1);
|
|
4491
4457
|
const retrySeed = buildSessionSeed(prior);
|
|
4492
|
-
const retryInstructions = retryThread.agent === "claude" ? null : formatAgentInstructions(
|
|
4493
|
-
loadAgentInstructions(retryThread.worktreePath, retryThread.agent)
|
|
4494
|
-
);
|
|
4495
4458
|
const retryPrefix = [
|
|
4496
4459
|
coordinatorDirective,
|
|
4497
4460
|
worktreeDirective,
|
|
4498
4461
|
artifactDirective,
|
|
4499
4462
|
renameBranchDirective,
|
|
4500
|
-
retryInstructions,
|
|
4501
4463
|
retrySeed
|
|
4502
4464
|
].filter(Boolean).join("\n\n---\n\n");
|
|
4503
4465
|
const retryHandle = await spawnAgentTurn(
|
|
@@ -5008,10 +4970,19 @@ var Orchestrator = class {
|
|
|
5008
4970
|
this.assertNotGlobal(thread, "Merge PR");
|
|
5009
4971
|
if (!selector) throw new Error("No pull request linked to this thread");
|
|
5010
4972
|
const result = await mergePr(cwd, selector);
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
4973
|
+
const state = normalizePrState(result.state) || "MERGED";
|
|
4974
|
+
const metaLike = {
|
|
4975
|
+
number: 0,
|
|
4976
|
+
title: thread.prTitle ?? thread.title,
|
|
4977
|
+
url: result.url || thread.prUrl || "",
|
|
4978
|
+
state,
|
|
4979
|
+
isDraft: false,
|
|
4980
|
+
reviewDecision: null,
|
|
4981
|
+
baseRefName: "",
|
|
4982
|
+
headRefName: ""
|
|
4983
|
+
};
|
|
4984
|
+
await this.persistPrMetaAndMaybeArchive(thread, metaLike);
|
|
4985
|
+
return { url: metaLike.url, state };
|
|
5015
4986
|
}
|
|
5016
4987
|
/** Resolve PR selector and optionally persist `prUrl` when found. */
|
|
5017
4988
|
async withPrSelector(threadRef) {
|
|
@@ -5033,19 +5004,55 @@ var Orchestrator = class {
|
|
|
5033
5004
|
if (!selector) return null;
|
|
5034
5005
|
const meta = await getPrMeta(cwd, selector);
|
|
5035
5006
|
if (meta) {
|
|
5036
|
-
|
|
5037
|
-
if (meta.url && meta.url !== thread.prUrl) patch.prUrl = meta.url;
|
|
5038
|
-
if (meta.title && meta.title !== thread.prTitle) patch.prTitle = meta.title;
|
|
5039
|
-
if (Object.keys(patch).length > 0) {
|
|
5040
|
-
updateThread(thread.id, patch);
|
|
5041
|
-
const latest = this.requireThread(thread.id);
|
|
5042
|
-
if (!latest.userSetTitle && meta.title && latest.title !== meta.title) {
|
|
5043
|
-
updateThread(thread.id, { title: meta.title });
|
|
5044
|
-
}
|
|
5045
|
-
}
|
|
5007
|
+
await this.persistPrMetaAndMaybeArchive(thread, meta);
|
|
5046
5008
|
}
|
|
5047
5009
|
return meta;
|
|
5048
5010
|
}
|
|
5011
|
+
/**
|
|
5012
|
+
* Persist PR URL/title/state and Conductor-style auto-archive when the PR
|
|
5013
|
+
* first becomes MERGED.
|
|
5014
|
+
*/
|
|
5015
|
+
async persistPrMetaAndMaybeArchive(thread, meta) {
|
|
5016
|
+
const prevState = normalizePrState(thread.prState);
|
|
5017
|
+
const nextState = normalizePrState(meta.state);
|
|
5018
|
+
const patch = {};
|
|
5019
|
+
if (meta.url && meta.url !== thread.prUrl) patch.prUrl = meta.url;
|
|
5020
|
+
if (meta.title && meta.title !== thread.prTitle) patch.prTitle = meta.title;
|
|
5021
|
+
if (nextState && nextState !== prevState) patch.prState = nextState;
|
|
5022
|
+
if (thread.skipAutoArchiveOnMerge && nextState && nextState !== "MERGED" && nextState !== "CLOSED") {
|
|
5023
|
+
patch.skipAutoArchiveOnMerge = false;
|
|
5024
|
+
}
|
|
5025
|
+
if (Object.keys(patch).length > 0) {
|
|
5026
|
+
updateThread(thread.id, patch);
|
|
5027
|
+
const latest2 = this.requireThread(thread.id);
|
|
5028
|
+
if (!latest2.userSetTitle && meta.title && latest2.title !== meta.title) {
|
|
5029
|
+
updateThread(thread.id, { title: meta.title });
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
const { autoArchiveOnMergeEnabled } = await import("../app-settings-GJVZGU3M.js");
|
|
5033
|
+
const latest = this.requireThread(thread.id);
|
|
5034
|
+
if (!shouldAutoArchiveOnPrMerge({
|
|
5035
|
+
previousPrState: prevState || null,
|
|
5036
|
+
nextPrState: nextState,
|
|
5037
|
+
threadStatus: latest.status,
|
|
5038
|
+
skipAutoArchiveOnMerge: latest.skipAutoArchiveOnMerge,
|
|
5039
|
+
autoArchiveEnabled: autoArchiveOnMergeEnabled(),
|
|
5040
|
+
isGlobal: isGlobalThread(latest)
|
|
5041
|
+
})) {
|
|
5042
|
+
return;
|
|
5043
|
+
}
|
|
5044
|
+
const siblings = threadsSharingWorktree(latest.worktreePath);
|
|
5045
|
+
for (const t of siblings) {
|
|
5046
|
+
const sibPatch = { prState: "MERGED" };
|
|
5047
|
+
if (meta.url && meta.url !== t.prUrl) sibPatch.prUrl = meta.url;
|
|
5048
|
+
if (meta.title && meta.title !== t.prTitle) sibPatch.prTitle = meta.title;
|
|
5049
|
+
if (Object.keys(sibPatch).length > 0) updateThread(t.id, sibPatch);
|
|
5050
|
+
}
|
|
5051
|
+
for (const t of siblings) {
|
|
5052
|
+
if (this.requireThread(t.id).status === "archived") continue;
|
|
5053
|
+
await this.archive(t.id);
|
|
5054
|
+
}
|
|
5055
|
+
}
|
|
5049
5056
|
async getPrStack(threadRef) {
|
|
5050
5057
|
const thread = this.requireThread(threadRef);
|
|
5051
5058
|
if (!thread.worktreePath?.trim()) return null;
|
|
@@ -5223,7 +5230,9 @@ var Orchestrator = class {
|
|
|
5223
5230
|
const thread = this.requireThread(threadRef);
|
|
5224
5231
|
this.stop(thread.id);
|
|
5225
5232
|
if (isGlobalThread(thread)) {
|
|
5226
|
-
|
|
5233
|
+
const archived2 = setStatus(thread.id, "archived");
|
|
5234
|
+
this.emit({ type: "status_changed", threadId: archived2.id, status: "archived" });
|
|
5235
|
+
return archived2;
|
|
5227
5236
|
}
|
|
5228
5237
|
const siblings = threadsSharingWorktree(thread.worktreePath).filter((t) => t.id !== thread.id);
|
|
5229
5238
|
if (siblings.length === 0) {
|
|
@@ -5241,9 +5250,10 @@ var Orchestrator = class {
|
|
|
5241
5250
|
await removeWorktree(thread.repoPath, thread.worktreePath);
|
|
5242
5251
|
}
|
|
5243
5252
|
const archived = setStatus(thread.id, "archived");
|
|
5253
|
+
this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
|
|
5244
5254
|
if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
|
|
5245
5255
|
try {
|
|
5246
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-
|
|
5256
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-RXFXQRRF.js");
|
|
5247
5257
|
await ensureWorkspace2(thread.repoPath);
|
|
5248
5258
|
} catch {
|
|
5249
5259
|
}
|
|
@@ -5264,7 +5274,7 @@ var Orchestrator = class {
|
|
|
5264
5274
|
await runArchiveScript(thread.repoPath, thread.worktreePath);
|
|
5265
5275
|
} catch {
|
|
5266
5276
|
}
|
|
5267
|
-
const { deleteBranchOnPurgeEnabled } = await import("../app-settings-
|
|
5277
|
+
const { deleteBranchOnPurgeEnabled } = await import("../app-settings-GJVZGU3M.js");
|
|
5268
5278
|
const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled();
|
|
5269
5279
|
await removeWorktree(thread.repoPath, thread.worktreePath, {
|
|
5270
5280
|
deleteBranch: deleteBranch ? thread.branchName : void 0
|
|
@@ -5280,10 +5290,12 @@ var Orchestrator = class {
|
|
|
5280
5290
|
if (isGlobalThread(thread)) {
|
|
5281
5291
|
const { globalAgentCwd } = await import("../paths-2OFB7UJG.js");
|
|
5282
5292
|
updateThread(thread.id, { worktreePath: globalAgentCwd() });
|
|
5283
|
-
|
|
5293
|
+
const restored2 = setStatus(thread.id, "idle");
|
|
5294
|
+
this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
|
|
5295
|
+
return restored2;
|
|
5284
5296
|
}
|
|
5285
5297
|
if (!existsSync13(thread.worktreePath)) {
|
|
5286
|
-
const { createThreadWorktree: createThreadWorktree2 } = await import("../worktree-
|
|
5298
|
+
const { createThreadWorktree: createThreadWorktree2 } = await import("../worktree-S2N63FLV.js");
|
|
5287
5299
|
const { execa: execa6 } = await import("execa");
|
|
5288
5300
|
const slug = thread.worktreePath.split("/").pop();
|
|
5289
5301
|
const dest = thread.worktreePath;
|
|
@@ -5293,7 +5305,31 @@ var Orchestrator = class {
|
|
|
5293
5305
|
void createThreadWorktree2;
|
|
5294
5306
|
void slug;
|
|
5295
5307
|
}
|
|
5296
|
-
|
|
5308
|
+
const restorePatch = {};
|
|
5309
|
+
let alreadyMerged = normalizePrState(thread.prState) === "MERGED";
|
|
5310
|
+
if (!alreadyMerged && thread.prUrl?.trim() && thread.worktreePath?.trim()) {
|
|
5311
|
+
try {
|
|
5312
|
+
const selector = resolvePrSelector(thread);
|
|
5313
|
+
if (selector) {
|
|
5314
|
+
const meta = await getPrMeta(thread.worktreePath, selector);
|
|
5315
|
+
if (meta) {
|
|
5316
|
+
const state = normalizePrState(meta.state);
|
|
5317
|
+
if (meta.url && meta.url !== thread.prUrl) restorePatch.prUrl = meta.url;
|
|
5318
|
+
if (meta.title && meta.title !== thread.prTitle) restorePatch.prTitle = meta.title;
|
|
5319
|
+
if (state) restorePatch.prState = state;
|
|
5320
|
+
if (state === "MERGED") alreadyMerged = true;
|
|
5321
|
+
}
|
|
5322
|
+
}
|
|
5323
|
+
} catch {
|
|
5324
|
+
}
|
|
5325
|
+
}
|
|
5326
|
+
if (alreadyMerged) restorePatch.skipAutoArchiveOnMerge = true;
|
|
5327
|
+
if (Object.keys(restorePatch).length > 0) {
|
|
5328
|
+
updateThread(thread.id, restorePatch);
|
|
5329
|
+
}
|
|
5330
|
+
const restored = setStatus(thread.id, "idle");
|
|
5331
|
+
this.emit({ type: "status_changed", threadId: restored.id, status: restored.status });
|
|
5332
|
+
return restored;
|
|
5297
5333
|
}
|
|
5298
5334
|
async attachCommand(threadRef) {
|
|
5299
5335
|
const thread = this.requireThread(threadRef);
|
|
@@ -5475,7 +5511,7 @@ function withTimeout(promise, ms, label) {
|
|
|
5475
5511
|
async function startMcpServer() {
|
|
5476
5512
|
const orch = getOrchestrator();
|
|
5477
5513
|
try {
|
|
5478
|
-
const { maxConcurrentAgents } = await import("../app-settings-
|
|
5514
|
+
const { maxConcurrentAgents } = await import("../app-settings-GJVZGU3M.js");
|
|
5479
5515
|
orch.setMaxConcurrent(maxConcurrentAgents());
|
|
5480
5516
|
} catch {
|
|
5481
5517
|
}
|
|
@@ -5566,14 +5602,13 @@ async function startMcpServer() {
|
|
|
5566
5602
|
),
|
|
5567
5603
|
artifact_id: z.string().optional().describe("Stable id when updating the same artifact across turns")
|
|
5568
5604
|
},
|
|
5569
|
-
async ({ title, type,
|
|
5605
|
+
async ({ title, type, artifact_id }) => {
|
|
5570
5606
|
const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
5571
5607
|
const payload = {
|
|
5572
5608
|
ok: true,
|
|
5573
5609
|
artifact_id: id,
|
|
5574
5610
|
title,
|
|
5575
5611
|
type,
|
|
5576
|
-
content,
|
|
5577
5612
|
message: "Artifact accepted. Sideboard desktop opens it in the side column beside chat."
|
|
5578
5613
|
};
|
|
5579
5614
|
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
@@ -5626,7 +5661,6 @@ async function startMcpServer() {
|
|
|
5626
5661
|
ok: true,
|
|
5627
5662
|
path,
|
|
5628
5663
|
title: title?.trim() || "Plan",
|
|
5629
|
-
content,
|
|
5630
5664
|
message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
|
|
5631
5665
|
};
|
|
5632
5666
|
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
@@ -5656,9 +5690,6 @@ async function startMcpServer() {
|
|
|
5656
5690
|
datasource: args.datasource ?? (args.resource ? "inline" : "brightsy"),
|
|
5657
5691
|
resource_id: args.resource_id,
|
|
5658
5692
|
record_id: args.record_id,
|
|
5659
|
-
resource: args.resource,
|
|
5660
|
-
record: args.record,
|
|
5661
|
-
records: args.records,
|
|
5662
5693
|
message: "Schema pane accepted. Sideboard desktop opens the CMS column beside chat."
|
|
5663
5694
|
};
|
|
5664
5695
|
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
@@ -5686,739 +5717,741 @@ async function startMcpServer() {
|
|
|
5686
5717
|
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
5687
5718
|
}
|
|
5688
5719
|
);
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
if (
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
+
if (sideboardMcpProfile() !== "worktree") {
|
|
5721
|
+
const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults } = await import("../app-settings-GJVZGU3M.js");
|
|
5722
|
+
const accountDefaults = resolveThreadDefaults();
|
|
5723
|
+
const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
|
|
5724
|
+
server.tool(
|
|
5725
|
+
"create_thread",
|
|
5726
|
+
`Create a new worktree thread (chat) from branch, pr, or ticket. Pass repoPath from list_workspaces. From an orchestration chat, omit parentThreadId (Sideboard binds the child to this chat) or pass the exact id from the turn reminder \u2014 never invent a uuid. Prefer omitting agent/model so Sideboard applies ${accountDefaultsHint}. Then use send_to_thread to chat.`,
|
|
5727
|
+
{
|
|
5728
|
+
sourceType: z.enum(["branch", "pr", "ticket"]),
|
|
5729
|
+
sourceRef: z.string(),
|
|
5730
|
+
agent: z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
|
|
5731
|
+
model: z.string().nullable().optional().describe(
|
|
5732
|
+
`Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
|
|
5733
|
+
),
|
|
5734
|
+
repoPath: z.string(),
|
|
5735
|
+
title: z.string().optional(),
|
|
5736
|
+
parentThreadId: z.string().optional().describe(
|
|
5737
|
+
"Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
|
|
5738
|
+
)
|
|
5739
|
+
},
|
|
5740
|
+
async (args) => {
|
|
5741
|
+
const envParentId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || "";
|
|
5742
|
+
let parentId = args.parentThreadId?.trim() || "";
|
|
5743
|
+
let parent = parentId ? orch.getThread(parentId) : null;
|
|
5744
|
+
let parentCorrectedFrom;
|
|
5745
|
+
if (envParentId) {
|
|
5746
|
+
const envParent = orch.getThread(envParentId);
|
|
5747
|
+
if (envParent) {
|
|
5748
|
+
if (parentId && parentId !== envParentId) parentCorrectedFrom = parentId;
|
|
5749
|
+
else if (!parentId) parentCorrectedFrom = void 0;
|
|
5750
|
+
parentId = envParentId;
|
|
5751
|
+
parent = envParent;
|
|
5752
|
+
}
|
|
5720
5753
|
}
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5754
|
+
if (parentId && !parent) {
|
|
5755
|
+
parentCorrectedFrom = parentId;
|
|
5756
|
+
parentId = "";
|
|
5757
|
+
parent = null;
|
|
5758
|
+
}
|
|
5759
|
+
if (parentId) {
|
|
5760
|
+
const children = orch.getThreads(false).filter((t) => t.parentThreadId === parentId);
|
|
5761
|
+
if (children.length >= MAX_ORCH_THREADS) {
|
|
5762
|
+
return {
|
|
5763
|
+
content: [
|
|
5764
|
+
{
|
|
5765
|
+
type: "text",
|
|
5766
|
+
text: `Thread-creation cap (${MAX_ORCH_THREADS}) reached for this orchestration session`
|
|
5767
|
+
}
|
|
5768
|
+
],
|
|
5769
|
+
isError: true
|
|
5770
|
+
};
|
|
5771
|
+
}
|
|
5772
|
+
}
|
|
5773
|
+
let agentArg = args.agent;
|
|
5774
|
+
let agentCoercedFrom;
|
|
5775
|
+
const resolvedProbe = resolveNewThreadOptions2({ agent: agentArg }).agent;
|
|
5776
|
+
if (resolvedProbe === "codex") {
|
|
5777
|
+
agentCoercedFrom = agentArg ?? "codex";
|
|
5778
|
+
const accountAgent = resolveNewThreadOptions2({}).agent;
|
|
5779
|
+
agentArg = accountAgent !== "codex" ? accountAgent : "cursor";
|
|
5780
|
+
}
|
|
5781
|
+
const opts = resolveNewThreadOptions2({
|
|
5782
|
+
agent: agentArg,
|
|
5783
|
+
model: args.model
|
|
5784
|
+
});
|
|
5785
|
+
try {
|
|
5786
|
+
const thread = await withTimeout(
|
|
5787
|
+
orch.createThread({
|
|
5788
|
+
sourceType: args.sourceType,
|
|
5789
|
+
sourceRef: args.sourceRef,
|
|
5790
|
+
agent: opts.agent,
|
|
5791
|
+
model: opts.model,
|
|
5792
|
+
effort: opts.effort,
|
|
5793
|
+
fast: opts.fast,
|
|
5794
|
+
repoPath: args.repoPath,
|
|
5795
|
+
title: args.title,
|
|
5796
|
+
parentThreadId: parentId || null
|
|
5797
|
+
}),
|
|
5798
|
+
CREATE_THREAD_TIMEOUT_MS,
|
|
5799
|
+
"create_thread"
|
|
5800
|
+
);
|
|
5801
|
+
const parentNote = parentCorrectedFrom ? parentId ? `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; nested under ${parentId}` : `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; created without parent` : void 0;
|
|
5802
|
+
return {
|
|
5803
|
+
content: [
|
|
5804
|
+
{
|
|
5805
|
+
type: "text",
|
|
5806
|
+
text: JSON.stringify({
|
|
5807
|
+
id: thread.id,
|
|
5808
|
+
title: thread.title,
|
|
5809
|
+
branchName: thread.branchName,
|
|
5810
|
+
worktreePath: thread.worktreePath,
|
|
5811
|
+
agent: thread.agent,
|
|
5812
|
+
model: thread.model,
|
|
5813
|
+
status: thread.status,
|
|
5814
|
+
link: `sideboard://thread/${thread.id}`,
|
|
5815
|
+
parentThreadId: thread.parentThreadId,
|
|
5816
|
+
...agentCoercedFrom ? {
|
|
5817
|
+
agentCoercedFrom,
|
|
5818
|
+
note: `Avoid nested Codex under a Codex orchestrator \u2014 used Account default agent=${thread.agent}`
|
|
5819
|
+
} : {},
|
|
5820
|
+
...parentCorrectedFrom ? { parentCorrectedFrom, parentNote } : {}
|
|
5821
|
+
})
|
|
5822
|
+
}
|
|
5823
|
+
]
|
|
5824
|
+
};
|
|
5825
|
+
} catch (err) {
|
|
5826
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5730
5827
|
return {
|
|
5731
5828
|
content: [
|
|
5732
5829
|
{
|
|
5733
5830
|
type: "text",
|
|
5734
|
-
text: `
|
|
5831
|
+
text: `create_thread failed: ${message}`
|
|
5735
5832
|
}
|
|
5736
5833
|
],
|
|
5737
5834
|
isError: true
|
|
5738
5835
|
};
|
|
5739
5836
|
}
|
|
5740
5837
|
}
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
agent: opts.agent,
|
|
5759
|
-
model: opts.model,
|
|
5760
|
-
effort: opts.effort,
|
|
5761
|
-
fast: opts.fast,
|
|
5762
|
-
repoPath: args.repoPath,
|
|
5763
|
-
title: args.title,
|
|
5764
|
-
parentThreadId: parentId || null
|
|
5765
|
-
}),
|
|
5766
|
-
CREATE_THREAD_TIMEOUT_MS,
|
|
5767
|
-
"create_thread"
|
|
5768
|
-
);
|
|
5769
|
-
const parentNote = parentCorrectedFrom ? parentId ? `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; nested under ${parentId}` : `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; created without parent` : void 0;
|
|
5838
|
+
);
|
|
5839
|
+
server.tool(
|
|
5840
|
+
"send_to_thread",
|
|
5841
|
+
"Queue a prompt on a worktree thread chat (runs under concurrency cap). Use after create_thread to start or continue a conversation. Set force_stop=true to interrupt an in-flight/queued turn (kill + clear queue) before queueing this prompt \u2014 use when the thread is mid-turn or has stale queued prompts you need to replace.",
|
|
5842
|
+
{
|
|
5843
|
+
ref: z.string(),
|
|
5844
|
+
prompt: z.string(),
|
|
5845
|
+
force_stop: z.boolean().optional()
|
|
5846
|
+
},
|
|
5847
|
+
async ({ ref, prompt, force_stop }) => {
|
|
5848
|
+
if (force_stop) {
|
|
5849
|
+
const existing = orch.getThread(ref);
|
|
5850
|
+
if (existing) {
|
|
5851
|
+
orch.stop(ref, { clearQueue: true });
|
|
5852
|
+
}
|
|
5853
|
+
}
|
|
5854
|
+
const thread = await orch.send(ref, prompt);
|
|
5770
5855
|
return {
|
|
5771
5856
|
content: [
|
|
5772
5857
|
{
|
|
5773
5858
|
type: "text",
|
|
5774
5859
|
text: JSON.stringify({
|
|
5775
5860
|
id: thread.id,
|
|
5776
|
-
title: thread.title,
|
|
5777
|
-
branchName: thread.branchName,
|
|
5778
|
-
worktreePath: thread.worktreePath,
|
|
5779
|
-
agent: thread.agent,
|
|
5780
|
-
model: thread.model,
|
|
5781
5861
|
status: thread.status,
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
...agentCoercedFrom ? {
|
|
5785
|
-
agentCoercedFrom,
|
|
5786
|
-
note: `Avoid nested Codex under a Codex orchestrator \u2014 used Account default agent=${thread.agent}`
|
|
5787
|
-
} : {},
|
|
5788
|
-
...parentCorrectedFrom ? { parentCorrectedFrom, parentNote } : {}
|
|
5862
|
+
queueLength: thread.queue.length,
|
|
5863
|
+
forceStopped: Boolean(force_stop)
|
|
5789
5864
|
})
|
|
5790
5865
|
}
|
|
5791
5866
|
]
|
|
5792
5867
|
};
|
|
5793
|
-
}
|
|
5794
|
-
|
|
5868
|
+
}
|
|
5869
|
+
);
|
|
5870
|
+
server.tool(
|
|
5871
|
+
"wait_for_turn",
|
|
5872
|
+
"Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread to read the agent reply.",
|
|
5873
|
+
{
|
|
5874
|
+
ref: z.string(),
|
|
5875
|
+
timeoutMs: z.number().optional()
|
|
5876
|
+
},
|
|
5877
|
+
async ({ ref, timeoutMs }) => {
|
|
5878
|
+
const thread = await orch.waitForTurn(ref, timeoutMs ?? 6e5);
|
|
5879
|
+
const result = orch.getTurnResult(thread.id);
|
|
5795
5880
|
return {
|
|
5796
5881
|
content: [
|
|
5797
5882
|
{
|
|
5798
5883
|
type: "text",
|
|
5799
|
-
text:
|
|
5884
|
+
text: JSON.stringify({
|
|
5885
|
+
id: thread.id,
|
|
5886
|
+
status: result.status,
|
|
5887
|
+
text: result.text
|
|
5888
|
+
})
|
|
5800
5889
|
}
|
|
5801
|
-
]
|
|
5802
|
-
isError: true
|
|
5890
|
+
]
|
|
5803
5891
|
};
|
|
5804
5892
|
}
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
force_stop: z.boolean().optional()
|
|
5814
|
-
},
|
|
5815
|
-
async ({ ref, prompt, force_stop }) => {
|
|
5816
|
-
if (force_stop) {
|
|
5817
|
-
const existing = orch.getThread(ref);
|
|
5818
|
-
if (existing) {
|
|
5819
|
-
orch.stop(ref, { clearQueue: true });
|
|
5820
|
-
}
|
|
5893
|
+
);
|
|
5894
|
+
server.tool(
|
|
5895
|
+
"get_turn_result",
|
|
5896
|
+
"Final assistant message only (not full transcript)",
|
|
5897
|
+
{ ref: z.string() },
|
|
5898
|
+
async ({ ref }) => {
|
|
5899
|
+
const result = orch.getTurnResult(ref);
|
|
5900
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
5821
5901
|
}
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
{
|
|
5842
|
-
ref: z.string(),
|
|
5843
|
-
timeoutMs: z.number().optional()
|
|
5844
|
-
},
|
|
5845
|
-
async ({ ref, timeoutMs }) => {
|
|
5846
|
-
const thread = await orch.waitForTurn(ref, timeoutMs ?? 6e5);
|
|
5847
|
-
const result = orch.getTurnResult(thread.id);
|
|
5848
|
-
return {
|
|
5849
|
-
content: [
|
|
5850
|
-
{
|
|
5851
|
-
type: "text",
|
|
5852
|
-
text: JSON.stringify({
|
|
5853
|
-
id: thread.id,
|
|
5854
|
-
status: result.status,
|
|
5855
|
-
text: result.text
|
|
5856
|
-
})
|
|
5857
|
-
}
|
|
5858
|
-
]
|
|
5859
|
-
};
|
|
5860
|
-
}
|
|
5861
|
-
);
|
|
5862
|
-
server.tool(
|
|
5863
|
-
"get_turn_result",
|
|
5864
|
-
"Final assistant message only (not full transcript)",
|
|
5865
|
-
{ ref: z.string() },
|
|
5866
|
-
async ({ ref }) => {
|
|
5867
|
-
const result = orch.getTurnResult(ref);
|
|
5868
|
-
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
5869
|
-
}
|
|
5870
|
-
);
|
|
5871
|
-
server.tool(
|
|
5872
|
-
"stop_thread",
|
|
5873
|
-
"Force-stop a thread: kill any in-flight agent turn AND clear queued prompts so drainQueue cannot continue. Does not archive the worktree. Optional force defaults to true.",
|
|
5874
|
-
{
|
|
5875
|
-
ref: z.string(),
|
|
5876
|
-
force: z.boolean().optional()
|
|
5877
|
-
},
|
|
5878
|
-
async ({ ref, force }) => {
|
|
5879
|
-
const t = orch.getThread(ref);
|
|
5880
|
-
if (!t) {
|
|
5902
|
+
);
|
|
5903
|
+
server.tool(
|
|
5904
|
+
"stop_thread",
|
|
5905
|
+
"Force-stop a thread: kill any in-flight agent turn AND clear queued prompts so drainQueue cannot continue. Does not archive the worktree. Optional force defaults to true.",
|
|
5906
|
+
{
|
|
5907
|
+
ref: z.string(),
|
|
5908
|
+
force: z.boolean().optional()
|
|
5909
|
+
},
|
|
5910
|
+
async ({ ref, force }) => {
|
|
5911
|
+
const t = orch.getThread(ref);
|
|
5912
|
+
if (!t) {
|
|
5913
|
+
return {
|
|
5914
|
+
content: [{ type: "text", text: `Thread not found: ${ref}` }],
|
|
5915
|
+
isError: true
|
|
5916
|
+
};
|
|
5917
|
+
}
|
|
5918
|
+
const clearQueue = force !== false;
|
|
5919
|
+
const hadQueued = t.queue.length > 0;
|
|
5920
|
+
const stopped = orch.stop(ref, { clearQueue });
|
|
5881
5921
|
return {
|
|
5882
|
-
content: [
|
|
5883
|
-
|
|
5922
|
+
content: [
|
|
5923
|
+
{
|
|
5924
|
+
type: "text",
|
|
5925
|
+
text: JSON.stringify({
|
|
5926
|
+
id: stopped.id,
|
|
5927
|
+
status: stopped.status,
|
|
5928
|
+
clearedQueue: clearQueue && hadQueued
|
|
5929
|
+
})
|
|
5930
|
+
}
|
|
5931
|
+
]
|
|
5884
5932
|
};
|
|
5885
5933
|
}
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
async ({ ref }) => {
|
|
5908
|
-
const t = orch.getThread(ref);
|
|
5909
|
-
if (!t) {
|
|
5934
|
+
);
|
|
5935
|
+
server.tool(
|
|
5936
|
+
"archive_thread",
|
|
5937
|
+
"Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Cannot archive the cloud coordinator \u2014 use the Sideboard UI for that.",
|
|
5938
|
+
{ ref: z.string() },
|
|
5939
|
+
async ({ ref }) => {
|
|
5940
|
+
const t = orch.getThread(ref);
|
|
5941
|
+
if (!t) {
|
|
5942
|
+
return {
|
|
5943
|
+
content: [{ type: "text", text: `Thread not found: ${ref}` }],
|
|
5944
|
+
isError: true
|
|
5945
|
+
};
|
|
5946
|
+
}
|
|
5947
|
+
const blocked = mcpArchiveBlockedReason(t);
|
|
5948
|
+
if (blocked) {
|
|
5949
|
+
return {
|
|
5950
|
+
content: [{ type: "text", text: blocked }],
|
|
5951
|
+
isError: true
|
|
5952
|
+
};
|
|
5953
|
+
}
|
|
5954
|
+
const archived = await orch.archive(ref);
|
|
5910
5955
|
return {
|
|
5911
|
-
content: [
|
|
5912
|
-
|
|
5956
|
+
content: [
|
|
5957
|
+
{
|
|
5958
|
+
type: "text",
|
|
5959
|
+
text: JSON.stringify({
|
|
5960
|
+
id: archived.id,
|
|
5961
|
+
status: archived.status
|
|
5962
|
+
})
|
|
5963
|
+
}
|
|
5964
|
+
]
|
|
5913
5965
|
};
|
|
5914
5966
|
}
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5967
|
+
);
|
|
5968
|
+
server.tool(
|
|
5969
|
+
"restore_thread",
|
|
5970
|
+
"Restore an archived thread (recreates worktree from branch when needed)",
|
|
5971
|
+
{ ref: z.string() },
|
|
5972
|
+
async ({ ref }) => {
|
|
5973
|
+
try {
|
|
5974
|
+
const restored = await orch.restore(ref);
|
|
5975
|
+
return {
|
|
5976
|
+
content: [
|
|
5977
|
+
{
|
|
5978
|
+
type: "text",
|
|
5979
|
+
text: JSON.stringify({
|
|
5980
|
+
id: restored.id,
|
|
5981
|
+
status: restored.status,
|
|
5982
|
+
worktreePath: restored.worktreePath
|
|
5983
|
+
})
|
|
5984
|
+
}
|
|
5985
|
+
]
|
|
5986
|
+
};
|
|
5987
|
+
} catch (err) {
|
|
5988
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5989
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
5990
|
+
}
|
|
5921
5991
|
}
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
]
|
|
5933
|
-
};
|
|
5934
|
-
}
|
|
5935
|
-
);
|
|
5936
|
-
server.tool(
|
|
5937
|
-
"restore_thread",
|
|
5938
|
-
"Restore an archived thread (recreates worktree from branch when needed)",
|
|
5939
|
-
{ ref: z.string() },
|
|
5940
|
-
async ({ ref }) => {
|
|
5941
|
-
try {
|
|
5942
|
-
const restored = await orch.restore(ref);
|
|
5992
|
+
);
|
|
5993
|
+
server.tool(
|
|
5994
|
+
"get_diff",
|
|
5995
|
+
"Compact diff summary (capped hunks, paginated)",
|
|
5996
|
+
{
|
|
5997
|
+
ref: z.string(),
|
|
5998
|
+
maxFiles: z.number().optional()
|
|
5999
|
+
},
|
|
6000
|
+
async ({ ref, maxFiles }) => {
|
|
6001
|
+
const summary = await orch.diffSummary(ref);
|
|
5943
6002
|
return {
|
|
5944
6003
|
content: [
|
|
5945
6004
|
{
|
|
5946
6005
|
type: "text",
|
|
5947
6006
|
text: JSON.stringify({
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
worktreePath: restored.worktreePath
|
|
6007
|
+
...summary,
|
|
6008
|
+
files: summary.files.slice(0, maxFiles ?? 10)
|
|
5951
6009
|
})
|
|
5952
6010
|
}
|
|
5953
6011
|
]
|
|
5954
6012
|
};
|
|
5955
|
-
} catch (err) {
|
|
5956
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
5957
|
-
return { content: [{ type: "text", text: message }], isError: true };
|
|
5958
6013
|
}
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
6014
|
+
);
|
|
6015
|
+
server.tool(
|
|
6016
|
+
"request_review",
|
|
6017
|
+
'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .sideboard/review.md when present (else local Review request.md / stock template), and sends "Review changes in this workspace." Expect Approve / Approve with nits / Request changes / Needs more information. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn / get_turn_result on the returned review tab id.',
|
|
6018
|
+
{ ref: z.string().describe("Worktree thread id/ref to review") },
|
|
6019
|
+
async ({ ref }) => {
|
|
6020
|
+
try {
|
|
6021
|
+
const tab = await orch.requestReview(ref);
|
|
6022
|
+
const from = orch.getThread(ref);
|
|
6023
|
+
return {
|
|
6024
|
+
content: [
|
|
6025
|
+
{
|
|
6026
|
+
type: "text",
|
|
6027
|
+
text: JSON.stringify({
|
|
6028
|
+
id: tab.id,
|
|
6029
|
+
title: tab.title,
|
|
6030
|
+
status: tab.status,
|
|
6031
|
+
fromThreadId: from?.id ?? ref,
|
|
6032
|
+
link: `sideboard://thread/${tab.id}`
|
|
6033
|
+
})
|
|
6034
|
+
}
|
|
6035
|
+
]
|
|
6036
|
+
};
|
|
6037
|
+
} catch (err) {
|
|
6038
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6039
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
6040
|
+
}
|
|
6041
|
+
}
|
|
6042
|
+
);
|
|
6043
|
+
const agentEnum = z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
6044
|
+
server.tool(
|
|
6045
|
+
"list_models",
|
|
6046
|
+
"List models for an agent. Prefer Auto: do not call this unless you have a reason to pin a specific model (user request, cost/latency, capability). Omit agent to list all.",
|
|
6047
|
+
{
|
|
6048
|
+
agent: agentEnum.optional().describe("Limit to one agent; omit for all")
|
|
6049
|
+
},
|
|
6050
|
+
async ({ agent }) => {
|
|
6051
|
+
try {
|
|
6052
|
+
const catalogs = await listModelsForAgent(agent);
|
|
6053
|
+
return {
|
|
6054
|
+
content: [{ type: "text", text: JSON.stringify(catalogs, null, 2) }]
|
|
6055
|
+
};
|
|
6056
|
+
} catch (err) {
|
|
6057
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6058
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
6059
|
+
}
|
|
6060
|
+
}
|
|
6061
|
+
);
|
|
6062
|
+
server.tool(
|
|
6063
|
+
"fork_worktree",
|
|
6064
|
+
"Fork a worktree agent chat into a NEW git worktree + chat (desktop \u201CFork to new workspace\u201D). Seeds a transcript (through through_index, default all). Optional agent override. Leave model unset for Auto (default) \u2014 only pass model when you have a reason. Not for the orchestrator. Then send_to_thread / wait_for_turn on the returned id.",
|
|
6065
|
+
{
|
|
6066
|
+
ref: z.string().describe("Worktree thread id/ref to fork"),
|
|
6067
|
+
through_index: z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
6068
|
+
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
6069
|
+
model: z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
6070
|
+
title: z.string().optional()
|
|
6071
|
+
},
|
|
6072
|
+
async ({ ref, through_index, agent, model, title }) => {
|
|
6073
|
+
try {
|
|
6074
|
+
const source = orch.getThread(ref);
|
|
6075
|
+
if (source) await orch.reconcile(source.repoPath, { drainQueues: false });
|
|
6076
|
+
const thread = await orch.forkThreadWorktree({
|
|
6077
|
+
threadId: ref,
|
|
6078
|
+
throughIndex: through_index,
|
|
6079
|
+
agent,
|
|
6080
|
+
model,
|
|
6081
|
+
title
|
|
6082
|
+
});
|
|
6083
|
+
return {
|
|
6084
|
+
content: [
|
|
6085
|
+
{
|
|
6086
|
+
type: "text",
|
|
6087
|
+
text: JSON.stringify({
|
|
6088
|
+
id: thread.id,
|
|
6089
|
+
title: thread.title,
|
|
6090
|
+
status: thread.status,
|
|
6091
|
+
agent: thread.agent,
|
|
6092
|
+
model: thread.model,
|
|
6093
|
+
branchName: thread.branchName,
|
|
6094
|
+
worktreePath: thread.worktreePath,
|
|
6095
|
+
fromThreadId: source?.id ?? ref,
|
|
6096
|
+
link: `sideboard://thread/${thread.id}`
|
|
6097
|
+
})
|
|
6098
|
+
}
|
|
6099
|
+
]
|
|
6100
|
+
};
|
|
6101
|
+
} catch (err) {
|
|
6102
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6103
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
6104
|
+
}
|
|
6105
|
+
}
|
|
6106
|
+
);
|
|
6107
|
+
server.tool(
|
|
6108
|
+
"fork_chat",
|
|
6109
|
+
"Fork a chat into a NEW tab on the SAME workspace: worktree agent \u2192 same worktree tab; Global orchestration chat \u2192 new orchestration chat (same synthetic home). Seeds a transcript; optional agent override. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Remote coordinators use this to continue an orchestration chat on another agent after session limits. Then send_to_thread / wait_for_turn on the returned id. Use fork_worktree only for worktree agents that need a new git worktree.",
|
|
6110
|
+
{
|
|
6111
|
+
ref: z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
6112
|
+
through_index: z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
6113
|
+
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
6114
|
+
model: z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
6115
|
+
title: z.string().optional()
|
|
6116
|
+
},
|
|
6117
|
+
async ({ ref, through_index, agent, model, title }) => {
|
|
6118
|
+
try {
|
|
6119
|
+
const source = orch.getThread(ref);
|
|
6120
|
+
if (!source) {
|
|
6121
|
+
return {
|
|
6122
|
+
content: [{ type: "text", text: `Thread not found: ${ref}` }],
|
|
6123
|
+
isError: true
|
|
6124
|
+
};
|
|
5978
6125
|
}
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
6126
|
+
const tab = orch.forkChatTab({
|
|
6127
|
+
threadId: source.id,
|
|
6128
|
+
throughIndex: through_index,
|
|
6129
|
+
agent,
|
|
6130
|
+
model,
|
|
6131
|
+
title
|
|
6132
|
+
});
|
|
6133
|
+
return {
|
|
6134
|
+
content: [
|
|
6135
|
+
{
|
|
6136
|
+
type: "text",
|
|
6137
|
+
text: JSON.stringify({
|
|
6138
|
+
id: tab.id,
|
|
6139
|
+
title: tab.title,
|
|
6140
|
+
status: tab.status,
|
|
6141
|
+
agent: tab.agent,
|
|
6142
|
+
model: tab.model,
|
|
6143
|
+
sourceType: tab.sourceType,
|
|
6144
|
+
worktreePath: tab.worktreePath,
|
|
6145
|
+
fromThreadId: source.id,
|
|
6146
|
+
link: `sideboard://thread/${tab.id}`
|
|
6147
|
+
})
|
|
6148
|
+
}
|
|
6149
|
+
]
|
|
6150
|
+
};
|
|
6151
|
+
} catch (err) {
|
|
6152
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6153
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
6154
|
+
}
|
|
6155
|
+
}
|
|
6156
|
+
);
|
|
6157
|
+
server.tool(
|
|
6158
|
+
"run_dev_script",
|
|
6159
|
+
"Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
|
|
6160
|
+
{
|
|
6161
|
+
ref: z.string(),
|
|
6162
|
+
name: z.string().optional()
|
|
6163
|
+
},
|
|
6164
|
+
async ({ ref, name }) => {
|
|
6165
|
+
const result = await orch.startDev(ref, name);
|
|
5991
6166
|
return {
|
|
5992
6167
|
content: [
|
|
5993
6168
|
{
|
|
5994
6169
|
type: "text",
|
|
5995
6170
|
text: JSON.stringify({
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
link: `sideboard://thread/${tab.id}`
|
|
6171
|
+
port: result.port,
|
|
6172
|
+
scriptName: result.scriptName,
|
|
6173
|
+
ports: result.ports,
|
|
6174
|
+
url: `http://localhost:${result.port}`
|
|
6001
6175
|
})
|
|
6002
6176
|
}
|
|
6003
6177
|
]
|
|
6004
6178
|
};
|
|
6005
|
-
} catch (err) {
|
|
6006
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
6007
|
-
return { content: [{ type: "text", text: message }], isError: true };
|
|
6008
6179
|
}
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
},
|
|
6018
|
-
async ({ agent }) => {
|
|
6019
|
-
try {
|
|
6020
|
-
const catalogs = await listModelsForAgent(agent);
|
|
6180
|
+
);
|
|
6181
|
+
server.tool(
|
|
6182
|
+
"list_run_scripts",
|
|
6183
|
+
"List named run scripts available for a thread",
|
|
6184
|
+
{ ref: z.string() },
|
|
6185
|
+
async ({ ref }) => {
|
|
6186
|
+
const scripts = orch.listThreadRunScripts(ref);
|
|
6187
|
+
const active = orch.getActiveRuns(ref);
|
|
6021
6188
|
return {
|
|
6022
|
-
content: [
|
|
6189
|
+
content: [
|
|
6190
|
+
{
|
|
6191
|
+
type: "text",
|
|
6192
|
+
text: JSON.stringify({ scripts, active }, null, 2)
|
|
6193
|
+
}
|
|
6194
|
+
]
|
|
6023
6195
|
};
|
|
6024
|
-
} catch (err) {
|
|
6025
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
6026
|
-
return { content: [{ type: "text", text: message }], isError: true };
|
|
6027
6196
|
}
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6197
|
+
);
|
|
6198
|
+
server.tool(
|
|
6199
|
+
"stop_dev_script",
|
|
6200
|
+
"Stop a running script for a thread (all scripts if name omitted)",
|
|
6201
|
+
{
|
|
6202
|
+
ref: z.string(),
|
|
6203
|
+
name: z.string().optional()
|
|
6204
|
+
},
|
|
6205
|
+
async ({ ref, name }) => {
|
|
6206
|
+
orch.stopDev(ref, name);
|
|
6207
|
+
return { content: [{ type: "text", text: "ok" }] };
|
|
6208
|
+
}
|
|
6209
|
+
);
|
|
6210
|
+
server.tool(
|
|
6211
|
+
"run_setup",
|
|
6212
|
+
"Re-run workspace setup (Sideboard/Conductor settings or .cursor/worktrees.json)",
|
|
6213
|
+
{ ref: z.string() },
|
|
6214
|
+
async ({ ref }) => {
|
|
6215
|
+
const result = await orch.runSetup(ref);
|
|
6216
|
+
return {
|
|
6217
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
6218
|
+
};
|
|
6219
|
+
}
|
|
6220
|
+
);
|
|
6221
|
+
server.tool(
|
|
6222
|
+
"add_workspace",
|
|
6223
|
+
"Register a git repo as a Sideboard workspace",
|
|
6224
|
+
{ repoPath: z.string() },
|
|
6225
|
+
async ({ repoPath }) => {
|
|
6226
|
+
const ws = await orch.addWorkspace(repoPath);
|
|
6227
|
+
return { content: [{ type: "text", text: JSON.stringify(ws) }] };
|
|
6228
|
+
}
|
|
6229
|
+
);
|
|
6230
|
+
server.tool(
|
|
6231
|
+
"remove_workspace",
|
|
6232
|
+
"Unregister a Sideboard workspace (does not archive threads)",
|
|
6233
|
+
{ repoPath: z.string() },
|
|
6234
|
+
async ({ repoPath }) => {
|
|
6235
|
+
orch.removeWorkspace(repoPath);
|
|
6236
|
+
return { content: [{ type: "text", text: "ok" }] };
|
|
6237
|
+
}
|
|
6238
|
+
);
|
|
6239
|
+
server.tool(
|
|
6240
|
+
"fanout",
|
|
6241
|
+
"Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
|
|
6242
|
+
{
|
|
6243
|
+
prompt: z.string(),
|
|
6244
|
+
agents: z.array(z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
|
|
6245
|
+
repoPath: z.string(),
|
|
6246
|
+
sourceType: z.enum(["branch", "pr", "ticket"]).optional(),
|
|
6247
|
+
sourceRef: z.string().optional(),
|
|
6248
|
+
title: z.string().optional()
|
|
6249
|
+
},
|
|
6250
|
+
async (args) => {
|
|
6251
|
+
const threads = await orch.bestOfN(args);
|
|
6252
|
+
return {
|
|
6253
|
+
content: [
|
|
6254
|
+
{
|
|
6255
|
+
type: "text",
|
|
6256
|
+
text: JSON.stringify(
|
|
6257
|
+
threads.map((t) => ({
|
|
6258
|
+
id: t.id,
|
|
6259
|
+
agent: t.agent,
|
|
6260
|
+
branchName: t.branchName,
|
|
6261
|
+
worktreePath: t.worktreePath
|
|
6262
|
+
})),
|
|
6263
|
+
null,
|
|
6264
|
+
2
|
|
6265
|
+
)
|
|
6266
|
+
}
|
|
6267
|
+
]
|
|
6268
|
+
};
|
|
6269
|
+
}
|
|
6270
|
+
);
|
|
6271
|
+
server.tool(
|
|
6272
|
+
"list_branches",
|
|
6273
|
+
"List git branches in a registered workspace. Pass repoPath from list_workspaces (unmerged into the default branch by default \u2014 for create_thread sourceType=branch).",
|
|
6274
|
+
{
|
|
6275
|
+
repoPath: z.string(),
|
|
6276
|
+
unmergedOnly: z.boolean().optional()
|
|
6277
|
+
},
|
|
6278
|
+
async ({ repoPath, unmergedOnly }) => {
|
|
6279
|
+
const root = await resolveRepoRoot(repoPath);
|
|
6280
|
+
const branches = await listBranches(root, {
|
|
6281
|
+
unmergedOnly: unmergedOnly !== false
|
|
6050
6282
|
});
|
|
6051
6283
|
return {
|
|
6052
6284
|
content: [
|
|
6053
6285
|
{
|
|
6054
6286
|
type: "text",
|
|
6055
|
-
text:
|
|
6056
|
-
id: thread.id,
|
|
6057
|
-
title: thread.title,
|
|
6058
|
-
status: thread.status,
|
|
6059
|
-
agent: thread.agent,
|
|
6060
|
-
model: thread.model,
|
|
6061
|
-
branchName: thread.branchName,
|
|
6062
|
-
worktreePath: thread.worktreePath,
|
|
6063
|
-
fromThreadId: source?.id ?? ref,
|
|
6064
|
-
link: `sideboard://thread/${thread.id}`
|
|
6065
|
-
})
|
|
6287
|
+
text: branches.map((b) => `${b.current ? "*" : " "} ${b.name}`).join("\n")
|
|
6066
6288
|
}
|
|
6067
6289
|
]
|
|
6068
6290
|
};
|
|
6069
|
-
} catch (err) {
|
|
6070
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
6071
|
-
return { content: [{ type: "text", text: message }], isError: true };
|
|
6072
6291
|
}
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6292
|
+
);
|
|
6293
|
+
server.tool(
|
|
6294
|
+
"list_prs",
|
|
6295
|
+
"List open GitHub PRs for a registered workspace. Pass repoPath from list_workspaces (uses gh against that repo remote). Then create_thread with sourceType=pr.",
|
|
6296
|
+
{ repoPath: z.string() },
|
|
6297
|
+
async ({ repoPath }) => {
|
|
6298
|
+
const root = await resolveRepoRoot(repoPath);
|
|
6299
|
+
const prs = await listPrs(root);
|
|
6300
|
+
return {
|
|
6301
|
+
content: [
|
|
6302
|
+
{
|
|
6303
|
+
type: "text",
|
|
6304
|
+
text: prs.map(
|
|
6305
|
+
(p) => `#${p.number} ${p.title} (${p.headRefName})${p.isCrossRepository ? " [fork]" : ""}`
|
|
6306
|
+
).join("\n")
|
|
6307
|
+
}
|
|
6308
|
+
]
|
|
6309
|
+
};
|
|
6310
|
+
}
|
|
6311
|
+
);
|
|
6312
|
+
server.tool(
|
|
6313
|
+
"get_pr_stack",
|
|
6314
|
+
"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.",
|
|
6315
|
+
{ ref: z.string() },
|
|
6316
|
+
async ({ ref }) => {
|
|
6317
|
+
const stack = await orch.getPrStack(ref);
|
|
6318
|
+
return {
|
|
6319
|
+
content: [{ type: "text", text: JSON.stringify(stack, null, 2) }]
|
|
6320
|
+
};
|
|
6321
|
+
}
|
|
6322
|
+
);
|
|
6323
|
+
server.tool(
|
|
6324
|
+
"open_pr_stack_layers",
|
|
6325
|
+
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
6326
|
+
{
|
|
6327
|
+
ref: z.string(),
|
|
6328
|
+
layer: z.number().int().positive().optional()
|
|
6329
|
+
},
|
|
6330
|
+
async ({ ref, layer }) => {
|
|
6331
|
+
const result = await orch.openPrStackLayers(ref, { layer });
|
|
6332
|
+
return {
|
|
6333
|
+
content: [
|
|
6334
|
+
{
|
|
6335
|
+
type: "text",
|
|
6336
|
+
text: JSON.stringify(
|
|
6337
|
+
{
|
|
6338
|
+
stackNumber: result.stack.stackNumber,
|
|
6339
|
+
trunk: result.stack.trunk,
|
|
6340
|
+
threads: result.threads.map((t) => ({
|
|
6341
|
+
id: t.id,
|
|
6342
|
+
title: t.title,
|
|
6343
|
+
branchName: t.branchName,
|
|
6344
|
+
stackLayer: t.stackLayer,
|
|
6345
|
+
worktreePath: t.worktreePath,
|
|
6346
|
+
prUrl: t.prUrl,
|
|
6347
|
+
link: `sideboard://thread/${t.id}`
|
|
6348
|
+
}))
|
|
6349
|
+
},
|
|
6350
|
+
null,
|
|
6351
|
+
2
|
|
6352
|
+
)
|
|
6353
|
+
}
|
|
6354
|
+
]
|
|
6355
|
+
};
|
|
6356
|
+
}
|
|
6357
|
+
);
|
|
6358
|
+
server.tool(
|
|
6359
|
+
"add_stack_layer",
|
|
6360
|
+
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
6361
|
+
{
|
|
6362
|
+
ref: z.string(),
|
|
6363
|
+
branchName: z.string(),
|
|
6364
|
+
title: z.string().optional()
|
|
6365
|
+
},
|
|
6366
|
+
async ({ ref, branchName, title }) => {
|
|
6367
|
+
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
6368
|
+
return {
|
|
6369
|
+
content: [
|
|
6370
|
+
{
|
|
6371
|
+
type: "text",
|
|
6372
|
+
text: JSON.stringify(
|
|
6373
|
+
{
|
|
6374
|
+
id: result.thread.id,
|
|
6375
|
+
title: result.thread.title,
|
|
6376
|
+
branchName: result.thread.branchName,
|
|
6377
|
+
stackLayer: result.thread.stackLayer,
|
|
6378
|
+
worktreePath: result.thread.worktreePath,
|
|
6379
|
+
link: `sideboard://thread/${result.thread.id}`
|
|
6380
|
+
},
|
|
6381
|
+
null,
|
|
6382
|
+
2
|
|
6383
|
+
)
|
|
6384
|
+
}
|
|
6385
|
+
]
|
|
6386
|
+
};
|
|
6387
|
+
}
|
|
6388
|
+
);
|
|
6389
|
+
server.tool(
|
|
6390
|
+
"create_pr_stack",
|
|
6391
|
+
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
6392
|
+
{
|
|
6393
|
+
repoPath: z.string(),
|
|
6394
|
+
branches: z.array(z.string()).min(1),
|
|
6395
|
+
agent: z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
|
|
6396
|
+
base: z.string().optional(),
|
|
6397
|
+
title: z.string().optional()
|
|
6398
|
+
},
|
|
6399
|
+
async (args) => {
|
|
6400
|
+
const result = await orch.createPrStack({
|
|
6401
|
+
repoPath: args.repoPath,
|
|
6402
|
+
branches: args.branches,
|
|
6403
|
+
agent: args.agent,
|
|
6404
|
+
base: args.base,
|
|
6405
|
+
title: args.title
|
|
6100
6406
|
});
|
|
6101
6407
|
return {
|
|
6102
6408
|
content: [
|
|
6103
6409
|
{
|
|
6104
6410
|
type: "text",
|
|
6105
|
-
text: JSON.stringify(
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6411
|
+
text: JSON.stringify(
|
|
6412
|
+
{
|
|
6413
|
+
stackNumber: result.stack.stackNumber,
|
|
6414
|
+
trunk: result.stack.trunk,
|
|
6415
|
+
threads: result.threads.map((t) => ({
|
|
6416
|
+
id: t.id,
|
|
6417
|
+
title: t.title,
|
|
6418
|
+
branchName: t.branchName,
|
|
6419
|
+
stackLayer: t.stackLayer,
|
|
6420
|
+
worktreePath: t.worktreePath,
|
|
6421
|
+
link: `sideboard://thread/${t.id}`
|
|
6422
|
+
}))
|
|
6423
|
+
},
|
|
6424
|
+
null,
|
|
6425
|
+
2
|
|
6426
|
+
)
|
|
6116
6427
|
}
|
|
6117
6428
|
]
|
|
6118
6429
|
};
|
|
6119
|
-
} catch (err) {
|
|
6120
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
6121
|
-
return { content: [{ type: "text", text: message }], isError: true };
|
|
6122
6430
|
}
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
}
|
|
6148
|
-
);
|
|
6149
|
-
server.tool(
|
|
6150
|
-
"list_run_scripts",
|
|
6151
|
-
"List named run scripts available for a thread",
|
|
6152
|
-
{ ref: z.string() },
|
|
6153
|
-
async ({ ref }) => {
|
|
6154
|
-
const scripts = orch.listThreadRunScripts(ref);
|
|
6155
|
-
const active = orch.getActiveRuns(ref);
|
|
6156
|
-
return {
|
|
6157
|
-
content: [
|
|
6158
|
-
{
|
|
6159
|
-
type: "text",
|
|
6160
|
-
text: JSON.stringify({ scripts, active }, null, 2)
|
|
6161
|
-
}
|
|
6162
|
-
]
|
|
6163
|
-
};
|
|
6164
|
-
}
|
|
6165
|
-
);
|
|
6166
|
-
server.tool(
|
|
6167
|
-
"stop_dev_script",
|
|
6168
|
-
"Stop a running script for a thread (all scripts if name omitted)",
|
|
6169
|
-
{
|
|
6170
|
-
ref: z.string(),
|
|
6171
|
-
name: z.string().optional()
|
|
6172
|
-
},
|
|
6173
|
-
async ({ ref, name }) => {
|
|
6174
|
-
orch.stopDev(ref, name);
|
|
6175
|
-
return { content: [{ type: "text", text: "ok" }] };
|
|
6176
|
-
}
|
|
6177
|
-
);
|
|
6178
|
-
server.tool(
|
|
6179
|
-
"run_setup",
|
|
6180
|
-
"Re-run workspace setup (Sideboard/Conductor settings or .cursor/worktrees.json)",
|
|
6181
|
-
{ ref: z.string() },
|
|
6182
|
-
async ({ ref }) => {
|
|
6183
|
-
const result = await orch.runSetup(ref);
|
|
6184
|
-
return {
|
|
6185
|
-
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
6186
|
-
};
|
|
6187
|
-
}
|
|
6188
|
-
);
|
|
6189
|
-
server.tool(
|
|
6190
|
-
"add_workspace",
|
|
6191
|
-
"Register a git repo as a Sideboard workspace",
|
|
6192
|
-
{ repoPath: z.string() },
|
|
6193
|
-
async ({ repoPath }) => {
|
|
6194
|
-
const ws = await orch.addWorkspace(repoPath);
|
|
6195
|
-
return { content: [{ type: "text", text: JSON.stringify(ws) }] };
|
|
6196
|
-
}
|
|
6197
|
-
);
|
|
6198
|
-
server.tool(
|
|
6199
|
-
"remove_workspace",
|
|
6200
|
-
"Unregister a Sideboard workspace (does not archive threads)",
|
|
6201
|
-
{ repoPath: z.string() },
|
|
6202
|
-
async ({ repoPath }) => {
|
|
6203
|
-
orch.removeWorkspace(repoPath);
|
|
6204
|
-
return { content: [{ type: "text", text: "ok" }] };
|
|
6205
|
-
}
|
|
6206
|
-
);
|
|
6207
|
-
server.tool(
|
|
6208
|
-
"fanout",
|
|
6209
|
-
"Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
|
|
6210
|
-
{
|
|
6211
|
-
prompt: z.string(),
|
|
6212
|
-
agents: z.array(z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
|
|
6213
|
-
repoPath: z.string(),
|
|
6214
|
-
sourceType: z.enum(["branch", "pr", "ticket"]).optional(),
|
|
6215
|
-
sourceRef: z.string().optional(),
|
|
6216
|
-
title: z.string().optional()
|
|
6217
|
-
},
|
|
6218
|
-
async (args) => {
|
|
6219
|
-
const threads = await orch.bestOfN(args);
|
|
6220
|
-
return {
|
|
6221
|
-
content: [
|
|
6222
|
-
{
|
|
6223
|
-
type: "text",
|
|
6224
|
-
text: JSON.stringify(
|
|
6225
|
-
threads.map((t) => ({
|
|
6226
|
-
id: t.id,
|
|
6227
|
-
agent: t.agent,
|
|
6228
|
-
branchName: t.branchName,
|
|
6229
|
-
worktreePath: t.worktreePath
|
|
6230
|
-
})),
|
|
6231
|
-
null,
|
|
6232
|
-
2
|
|
6233
|
-
)
|
|
6234
|
-
}
|
|
6235
|
-
]
|
|
6236
|
-
};
|
|
6237
|
-
}
|
|
6238
|
-
);
|
|
6239
|
-
server.tool(
|
|
6240
|
-
"list_branches",
|
|
6241
|
-
"List git branches in a registered workspace. Pass repoPath from list_workspaces (unmerged into the default branch by default \u2014 for create_thread sourceType=branch).",
|
|
6242
|
-
{
|
|
6243
|
-
repoPath: z.string(),
|
|
6244
|
-
unmergedOnly: z.boolean().optional()
|
|
6245
|
-
},
|
|
6246
|
-
async ({ repoPath, unmergedOnly }) => {
|
|
6247
|
-
const root = await resolveRepoRoot(repoPath);
|
|
6248
|
-
const branches = await listBranches(root, {
|
|
6249
|
-
unmergedOnly: unmergedOnly !== false
|
|
6250
|
-
});
|
|
6251
|
-
return {
|
|
6252
|
-
content: [
|
|
6253
|
-
{
|
|
6254
|
-
type: "text",
|
|
6255
|
-
text: branches.map((b) => `${b.current ? "*" : " "} ${b.name}`).join("\n")
|
|
6256
|
-
}
|
|
6257
|
-
]
|
|
6258
|
-
};
|
|
6259
|
-
}
|
|
6260
|
-
);
|
|
6261
|
-
server.tool(
|
|
6262
|
-
"list_prs",
|
|
6263
|
-
"List open GitHub PRs for a registered workspace. Pass repoPath from list_workspaces (uses gh against that repo remote). Then create_thread with sourceType=pr.",
|
|
6264
|
-
{ repoPath: z.string() },
|
|
6265
|
-
async ({ repoPath }) => {
|
|
6266
|
-
const root = await resolveRepoRoot(repoPath);
|
|
6267
|
-
const prs = await listPrs(root);
|
|
6268
|
-
return {
|
|
6269
|
-
content: [
|
|
6270
|
-
{
|
|
6271
|
-
type: "text",
|
|
6272
|
-
text: prs.map(
|
|
6273
|
-
(p) => `#${p.number} ${p.title} (${p.headRefName})${p.isCrossRepository ? " [fork]" : ""}`
|
|
6274
|
-
).join("\n")
|
|
6275
|
-
}
|
|
6276
|
-
]
|
|
6277
|
-
};
|
|
6278
|
-
}
|
|
6279
|
-
);
|
|
6280
|
-
server.tool(
|
|
6281
|
-
"get_pr_stack",
|
|
6282
|
-
"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.",
|
|
6283
|
-
{ ref: z.string() },
|
|
6284
|
-
async ({ ref }) => {
|
|
6285
|
-
const stack = await orch.getPrStack(ref);
|
|
6286
|
-
return {
|
|
6287
|
-
content: [{ type: "text", text: JSON.stringify(stack, null, 2) }]
|
|
6288
|
-
};
|
|
6289
|
-
}
|
|
6290
|
-
);
|
|
6291
|
-
server.tool(
|
|
6292
|
-
"open_pr_stack_layers",
|
|
6293
|
-
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
6294
|
-
{
|
|
6295
|
-
ref: z.string(),
|
|
6296
|
-
layer: z.number().int().positive().optional()
|
|
6297
|
-
},
|
|
6298
|
-
async ({ ref, layer }) => {
|
|
6299
|
-
const result = await orch.openPrStackLayers(ref, { layer });
|
|
6300
|
-
return {
|
|
6301
|
-
content: [
|
|
6302
|
-
{
|
|
6303
|
-
type: "text",
|
|
6304
|
-
text: JSON.stringify(
|
|
6305
|
-
{
|
|
6306
|
-
stackNumber: result.stack.stackNumber,
|
|
6307
|
-
trunk: result.stack.trunk,
|
|
6308
|
-
threads: result.threads.map((t) => ({
|
|
6309
|
-
id: t.id,
|
|
6310
|
-
title: t.title,
|
|
6311
|
-
branchName: t.branchName,
|
|
6312
|
-
stackLayer: t.stackLayer,
|
|
6313
|
-
worktreePath: t.worktreePath,
|
|
6314
|
-
prUrl: t.prUrl,
|
|
6315
|
-
link: `sideboard://thread/${t.id}`
|
|
6316
|
-
}))
|
|
6317
|
-
},
|
|
6318
|
-
null,
|
|
6319
|
-
2
|
|
6320
|
-
)
|
|
6321
|
-
}
|
|
6322
|
-
]
|
|
6323
|
-
};
|
|
6324
|
-
}
|
|
6325
|
-
);
|
|
6326
|
-
server.tool(
|
|
6327
|
-
"add_stack_layer",
|
|
6328
|
-
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
6329
|
-
{
|
|
6330
|
-
ref: z.string(),
|
|
6331
|
-
branchName: z.string(),
|
|
6332
|
-
title: z.string().optional()
|
|
6333
|
-
},
|
|
6334
|
-
async ({ ref, branchName, title }) => {
|
|
6335
|
-
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
6336
|
-
return {
|
|
6337
|
-
content: [
|
|
6338
|
-
{
|
|
6339
|
-
type: "text",
|
|
6340
|
-
text: JSON.stringify(
|
|
6341
|
-
{
|
|
6342
|
-
id: result.thread.id,
|
|
6343
|
-
title: result.thread.title,
|
|
6344
|
-
branchName: result.thread.branchName,
|
|
6345
|
-
stackLayer: result.thread.stackLayer,
|
|
6346
|
-
worktreePath: result.thread.worktreePath,
|
|
6347
|
-
link: `sideboard://thread/${result.thread.id}`
|
|
6348
|
-
},
|
|
6349
|
-
null,
|
|
6350
|
-
2
|
|
6351
|
-
)
|
|
6352
|
-
}
|
|
6353
|
-
]
|
|
6354
|
-
};
|
|
6355
|
-
}
|
|
6356
|
-
);
|
|
6357
|
-
server.tool(
|
|
6358
|
-
"create_pr_stack",
|
|
6359
|
-
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
6360
|
-
{
|
|
6361
|
-
repoPath: z.string(),
|
|
6362
|
-
branches: z.array(z.string()).min(1),
|
|
6363
|
-
agent: z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
|
|
6364
|
-
base: z.string().optional(),
|
|
6365
|
-
title: z.string().optional()
|
|
6366
|
-
},
|
|
6367
|
-
async (args) => {
|
|
6368
|
-
const result = await orch.createPrStack({
|
|
6369
|
-
repoPath: args.repoPath,
|
|
6370
|
-
branches: args.branches,
|
|
6371
|
-
agent: args.agent,
|
|
6372
|
-
base: args.base,
|
|
6373
|
-
title: args.title
|
|
6374
|
-
});
|
|
6375
|
-
return {
|
|
6376
|
-
content: [
|
|
6377
|
-
{
|
|
6378
|
-
type: "text",
|
|
6379
|
-
text: JSON.stringify(
|
|
6380
|
-
{
|
|
6381
|
-
stackNumber: result.stack.stackNumber,
|
|
6382
|
-
trunk: result.stack.trunk,
|
|
6383
|
-
threads: result.threads.map((t) => ({
|
|
6384
|
-
id: t.id,
|
|
6385
|
-
title: t.title,
|
|
6386
|
-
branchName: t.branchName,
|
|
6387
|
-
stackLayer: t.stackLayer,
|
|
6388
|
-
worktreePath: t.worktreePath,
|
|
6389
|
-
link: `sideboard://thread/${t.id}`
|
|
6390
|
-
}))
|
|
6391
|
-
},
|
|
6392
|
-
null,
|
|
6393
|
-
2
|
|
6394
|
-
)
|
|
6395
|
-
}
|
|
6396
|
-
]
|
|
6397
|
-
};
|
|
6398
|
-
}
|
|
6399
|
-
);
|
|
6400
|
-
server.tool(
|
|
6401
|
-
"list_issues",
|
|
6402
|
-
"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.",
|
|
6403
|
-
{ repoPath: z.string() },
|
|
6404
|
-
async ({ repoPath }) => {
|
|
6405
|
-
const root = await resolveRepoRoot(repoPath);
|
|
6406
|
-
const result = await listIssues(root);
|
|
6407
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
6408
|
-
}
|
|
6409
|
-
);
|
|
6410
|
-
server.tool(
|
|
6411
|
-
"list_linear_issues",
|
|
6412
|
-
"Deprecated: prefer list_issues. Lists assigned Linear issues via the chosen agent MCP connector",
|
|
6413
|
-
{
|
|
6414
|
-
agent: z.enum(["claude", "codex", "opencode"]),
|
|
6415
|
-
repoPath: z.string()
|
|
6416
|
-
},
|
|
6417
|
-
async ({ agent, repoPath }) => {
|
|
6418
|
-
const issues = await listLinearIssues(agent, repoPath);
|
|
6419
|
-
return { content: [{ type: "text", text: JSON.stringify(issues, null, 2) }] };
|
|
6420
|
-
}
|
|
6421
|
-
);
|
|
6431
|
+
);
|
|
6432
|
+
server.tool(
|
|
6433
|
+
"list_issues",
|
|
6434
|
+
"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.",
|
|
6435
|
+
{ repoPath: z.string() },
|
|
6436
|
+
async ({ repoPath }) => {
|
|
6437
|
+
const root = await resolveRepoRoot(repoPath);
|
|
6438
|
+
const result = await listIssues(root);
|
|
6439
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
6440
|
+
}
|
|
6441
|
+
);
|
|
6442
|
+
server.tool(
|
|
6443
|
+
"list_linear_issues",
|
|
6444
|
+
"Deprecated: prefer list_issues. Lists assigned Linear issues via the chosen agent MCP connector",
|
|
6445
|
+
{
|
|
6446
|
+
agent: z.enum(["claude", "codex", "opencode"]),
|
|
6447
|
+
repoPath: z.string()
|
|
6448
|
+
},
|
|
6449
|
+
async ({ agent, repoPath }) => {
|
|
6450
|
+
const issues = await listLinearIssues(agent, repoPath);
|
|
6451
|
+
return { content: [{ type: "text", text: JSON.stringify(issues, null, 2) }] };
|
|
6452
|
+
}
|
|
6453
|
+
);
|
|
6454
|
+
}
|
|
6422
6455
|
const transport = new StdioServerTransport();
|
|
6423
6456
|
await server.connect(transport);
|
|
6424
6457
|
}
|