@sideboard-ai/core 0.1.62 → 0.1.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/{agents-KF3J3C53.js → agents-OTBZVTZO.js} +27 -7
  2. package/dist/{agents-QLSFIQAO.js → agents-TS3EI2YI.js} +27 -7
  3. package/dist/{app-settings-LTSFG7QJ.js → app-settings-GJVZGU3M.js} +13 -3
  4. package/dist/{app-settings-HK4KAM4R.js → app-settings-ONKMFSEJ.js} +13 -3
  5. package/dist/{chunk-UGXSSBFJ.js → chunk-4BUCGNZS.js} +1 -1
  6. package/dist/chunk-4UIYN56C.js +173 -0
  7. package/dist/{chunk-NVU7EC2E.js → chunk-57WWNMFP.js} +2 -2
  8. package/dist/{chunk-A6FOKNOU.js → chunk-5HTTCYWA.js} +2 -2
  9. package/dist/{chunk-DXTGDXO2.js → chunk-7ZOQY4A7.js} +31 -6
  10. package/dist/{chunk-ZF5YJCXO.js → chunk-B4PRRKMA.js} +284 -93
  11. package/dist/{chunk-DDN3UF4R.js → chunk-BPOTVI5J.js} +115 -3
  12. package/dist/{chunk-MBAL5FE6.js → chunk-DB3I75FI.js} +4 -4
  13. package/dist/chunk-FGDJOJ2L.js +176 -0
  14. package/dist/{chunk-2FEA4LDA.js → chunk-JWZNK3CQ.js} +31 -6
  15. package/dist/{chunk-2MCNURQV.js → chunk-K7LM4CLD.js} +279 -93
  16. package/dist/{chunk-3KKNTHUC.js → chunk-LW5CMQGZ.js} +1 -1
  17. package/dist/{chunk-JB2H7E7V.js → chunk-M5V4QIWF.js} +1 -1
  18. package/dist/{chunk-6CUJQKTJ.js → chunk-MHR4OGZ4.js} +115 -3
  19. package/dist/{chunk-46KMKHSD.js → chunk-WAFIF22N.js} +2 -2
  20. package/dist/{chunk-27MAWNKY.js → chunk-ZRFAZVGL.js} +2 -2
  21. package/dist/{connected-teams-MKIYKOYA.js → connected-teams-OMDHTL6A.js} +2 -2
  22. package/dist/{connected-teams-RSL4VB3X.js → connected-teams-RUNMOCG5.js} +2 -2
  23. package/dist/{coordinator-prompt-N2R6FGZK.js → coordinator-prompt-3BSMKZL4.js} +4 -4
  24. package/dist/{coordinator-prompt-LKG3SHMI.js → coordinator-prompt-6HMTOX4G.js} +4 -4
  25. package/dist/{global-workspace-LALWQWRJ.js → global-workspace-QS7A23CB.js} +5 -6
  26. package/dist/{global-workspace-YOAUQCMA.js → global-workspace-YZ2NJVLL.js} +5 -6
  27. package/dist/index.cjs +1345 -933
  28. package/dist/index.d.cts +96 -1
  29. package/dist/index.d.ts +96 -1
  30. package/dist/index.js +750 -715
  31. package/dist/mcp/run-stdio.cjs +1293 -977
  32. package/dist/mcp/run-stdio.js +691 -748
  33. package/dist/{run-GPSVV5OH.js → run-JLD2Y52J.js} +1 -1
  34. package/dist/{run-HZTBWGQR.js → run-Y2MI3EVV.js} +1 -1
  35. package/dist/{workspaces-E75POJXP.js → workspaces-AIUYUXB7.js} +6 -7
  36. package/dist/{workspaces-VCAIHVIE.js → workspaces-EBFW7I7S.js} +6 -7
  37. package/dist/{worktree-MV4WRW6G.js → worktree-7NBPIVFZ.js} +2 -2
  38. package/dist/{worktree-LZEGVQSN.js → worktree-WMDJW2BL.js} +2 -2
  39. package/package.json +1 -1
  40. package/dist/chunk-73AT5K4A.js +0 -104
  41. package/dist/chunk-GI7E5733.js +0 -30
  42. package/dist/chunk-RIWUU7FO.js +0 -107
  43. package/dist/chunk-S2LL5HA4.js +0 -33
@@ -14,15 +14,16 @@ import {
14
14
  parseSessionQuotaResetAt,
15
15
  pushTurnStderr,
16
16
  resolveQuotaFallbackAgent,
17
+ sideboardMcpProfile,
17
18
  summarizeTurnStderr
18
- } from "../chunk-2MCNURQV.js";
19
- import "../chunk-JB2H7E7V.js";
19
+ } from "../chunk-K7LM4CLD.js";
20
+ import "../chunk-M5V4QIWF.js";
20
21
  import {
21
22
  addWorkspace,
22
23
  ensureWorkspace,
23
24
  removeWorkspace,
24
25
  syncWorkspacesFromThreads
25
- } from "../chunk-A6FOKNOU.js";
26
+ } from "../chunk-5HTTCYWA.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-2FEA4LDA.js";
40
- import {
41
- assertOrchestratorCapableAgent
42
- } from "../chunk-S2LL5HA4.js";
41
+ } from "../chunk-JWZNK3CQ.js";
43
42
  import {
44
43
  coordinatorSystemPrompt,
45
44
  coordinatorTurnReminder,
46
45
  enrichWorkspacesWithGithub,
47
46
  ensureGlobalCoordinatorCwd
48
- } from "../chunk-NVU7EC2E.js";
47
+ } from "../chunk-57WWNMFP.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-3KKNTHUC.js";
84
+ } from "../chunk-LW5CMQGZ.js";
86
85
  import {
87
86
  ATTACHMENTS_DIR,
88
87
  LEGACY_ATTACHMENTS_DIR,
@@ -101,11 +100,12 @@ import {
101
100
  writeThread
102
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-RIWUU7FO.js";
108
+ } from "../chunk-FGDJOJ2L.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-DDN3UF4R.js";
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-YOAUQCMA.js");
394
+ const { isGlobalThread: isGlobalThread2 } = await import("../global-workspace-YZ2NJVLL.js");
395
395
  if (isGlobalThread2(thread)) {
396
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("../coordinator-prompt-LKG3SHMI.js");
396
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("../coordinator-prompt-6HMTOX4G.js");
397
397
  ensureGlobalCoordinatorCwd2(
398
398
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
399
399
  );
@@ -1117,6 +1117,7 @@ import { existsSync as existsSync5 } from "fs";
1117
1117
  var REQUIRE_AGENT_TIMEOUT_MS = 8e3;
1118
1118
  async function requireAgent(agent, opts) {
1119
1119
  ensureAgentPath();
1120
+ enrichPathWithNpmGlobalBin();
1120
1121
  const status = await Promise.race([
1121
1122
  getAdapter(agent).detect(),
1122
1123
  new Promise((_, reject) => {
@@ -1208,7 +1209,7 @@ async function createThread(input, _onSetupLine) {
1208
1209
  return readThread(thread.id) ?? thread;
1209
1210
  }
1210
1211
  async function listLinearIssues(agent, repoPath) {
1211
- const { getAdapter: getAdapter2 } = await import("../agents-KF3J3C53.js");
1212
+ const { getAdapter: getAdapter2 } = await import("../agents-OTBZVTZO.js");
1212
1213
  await requireAgent(agent, { requireLinear: true });
1213
1214
  const adapter = getAdapter2(agent);
1214
1215
  if (!adapter.listLinearIssues) {
@@ -2057,7 +2058,7 @@ async function adoptThread(input) {
2057
2058
  throw new Error(`Worktree not found: ${input.worktreePath}`);
2058
2059
  }
2059
2060
  const repoPath = await resolveRepoRoot(input.worktreePath);
2060
- const { stdout: branch } = await import("../run-HZTBWGQR.js").then(
2061
+ const { stdout: branch } = await import("../run-Y2MI3EVV.js").then(
2061
2062
  ({ git: git2 }) => git2(["rev-parse", "--abbrev-ref", "HEAD"], input.worktreePath)
2062
2063
  );
2063
2064
  const thread = createEmptyThread({
@@ -2072,7 +2073,7 @@ async function adoptThread(input) {
2072
2073
  messages: input.messages ?? []
2073
2074
  });
2074
2075
  writeThread(thread);
2075
- const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-E75POJXP.js");
2076
+ const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-AIUYUXB7.js");
2076
2077
  await ensureWorkspace2(repoPath);
2077
2078
  return thread;
2078
2079
  }
@@ -3178,7 +3179,7 @@ async function previewLand(thread) {
3178
3179
  const target = await resolveDefaultBranch(thread.repoPath);
3179
3180
  if (thread.branchName === target || thread.branchName === "main" || thread.branchName === "master") {
3180
3181
  }
3181
- const { stdout: head } = await import("../run-HZTBWGQR.js").then(
3182
+ const { stdout: head } = await import("../run-Y2MI3EVV.js").then(
3182
3183
  ({ git: git2 }) => git2(["rev-parse", "--abbrev-ref", "HEAD"], thread.worktreePath)
3183
3184
  );
3184
3185
  const current = head.trim();
@@ -3217,7 +3218,7 @@ async function confirmLand(thread, opts) {
3217
3218
  if (preview.dirty) {
3218
3219
  committed = await commitAll(thread.worktreePath, meta.commitMessage);
3219
3220
  }
3220
- const { git: git2 } = await import("../run-HZTBWGQR.js");
3221
+ const { git: git2 } = await import("../run-Y2MI3EVV.js");
3221
3222
  const { stdout: headOut } = await git2(
3222
3223
  ["rev-parse", "--abbrev-ref", "HEAD"],
3223
3224
  thread.worktreePath,
@@ -3754,6 +3755,9 @@ function formatWorktreeDirective(thread, opts) {
3754
3755
  );
3755
3756
  return lines.join("\n");
3756
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
+ }
3757
3761
  function formatArtifactDirective() {
3758
3762
  return [
3759
3763
  "Sideboard side column (desktop UI):",
@@ -3774,60 +3778,6 @@ function formatArtifactDirective() {
3774
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."
3775
3779
  ].join("\n");
3776
3780
  }
3777
- var FILES_BY_AGENT = {
3778
- claude: [
3779
- "CLAUDE.md",
3780
- ".claude/CLAUDE.md",
3781
- "CLAUDE.local.md",
3782
- "AGENTS.md",
3783
- "AGENT.md"
3784
- ],
3785
- codex: ["AGENTS.md", "AGENT.md", ".codex/AGENTS.md", "CLAUDE.md"],
3786
- opencode: ["AGENTS.md", "AGENT.md", "OPENCODE.md", "CLAUDE.md"],
3787
- brightsy: ["AGENTS.md", "AGENT.md", "CLAUDE.md"],
3788
- cursor: ["AGENTS.md", "AGENT.md", ".cursor/rules", "CLAUDE.md"]
3789
- };
3790
- var MAX_CHARS_PER_FILE = 48e3;
3791
- function loadAgentInstructions(worktreePath, agent) {
3792
- const candidates = FILES_BY_AGENT[agent] ?? FILES_BY_AGENT.claude;
3793
- const seen = /* @__PURE__ */ new Set();
3794
- const out = [];
3795
- for (const rel of candidates) {
3796
- if (seen.has(rel)) continue;
3797
- const abs = join10(worktreePath, rel);
3798
- if (!existsSync12(abs)) continue;
3799
- try {
3800
- if (!statSync5(abs).isFile()) continue;
3801
- let content = readFileSync8(abs, "utf8");
3802
- if (!content.trim()) continue;
3803
- if (content.length > MAX_CHARS_PER_FILE) {
3804
- content = `${content.slice(0, MAX_CHARS_PER_FILE)}
3805
-
3806
- \u2026(truncated)`;
3807
- }
3808
- seen.add(rel);
3809
- out.push({ relativePath: rel, content });
3810
- } catch {
3811
- }
3812
- }
3813
- return out;
3814
- }
3815
- function formatAgentInstructions(files) {
3816
- if (files.length === 0) return null;
3817
- const parts = [
3818
- "Project agent instructions (from the worktree \u2014 follow these):",
3819
- ""
3820
- ];
3821
- for (const f of files) {
3822
- parts.push(`## ${f.relativePath}`);
3823
- parts.push("");
3824
- parts.push(f.content.trim());
3825
- parts.push("");
3826
- parts.push("---");
3827
- parts.push("");
3828
- }
3829
- return parts.join("\n").trimEnd();
3830
- }
3831
3781
 
3832
3782
  // src/threads/sync-branch.ts
3833
3783
  async function syncThreadBranchFromGit(threadId) {
@@ -3960,7 +3910,7 @@ var Orchestrator = class {
3960
3910
  orphans: orphans.map((o) => ({ path: o.path, repoPath: o.repoPath }))
3961
3911
  });
3962
3912
  }
3963
- const { autoCleanupOrphansEnabled } = await import("../app-settings-LTSFG7QJ.js");
3913
+ const { autoCleanupOrphansEnabled } = await import("../app-settings-GJVZGU3M.js");
3964
3914
  if (autoCleanupOrphansEnabled() && shouldRunWorktreeCleanup() && orphans.length > 0) {
3965
3915
  await cleanupOrphanWorktrees({ repoPaths });
3966
3916
  }
@@ -4117,7 +4067,7 @@ var Orchestrator = class {
4117
4067
  }
4118
4068
  async finishCreateThread(threadId, prompt) {
4119
4069
  await this.runSetupAfterCreate(threadId);
4120
- const { autoRunAfterSetupEnabled } = await import("../app-settings-LTSFG7QJ.js");
4070
+ const { autoRunAfterSetupEnabled } = await import("../app-settings-GJVZGU3M.js");
4121
4071
  if (autoRunAfterSetupEnabled()) {
4122
4072
  try {
4123
4073
  await this.startDev(threadId);
@@ -4358,13 +4308,14 @@ var Orchestrator = class {
4358
4308
  "Files column: MCP present_files (brightsy account storage or memory).",
4359
4309
  "Do not say artifacts/CMS UI are unavailable."
4360
4310
  ].join(" ") : null;
4311
+ const worktreeReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatWorktreeReminder() : null;
4361
4312
  const agentPrompt = [
4362
4313
  thread.planMode ? PLAN_MODE_INSTRUCTION : null,
4363
4314
  orchestrationReminder,
4315
+ worktreeReminder,
4364
4316
  artifactReminder,
4365
4317
  expandedPrompt
4366
4318
  ].filter(Boolean).join("\n\n");
4367
- const instructionFiles = loadAgentInstructions(thread.worktreePath, thread.agent);
4368
4319
  if (thread.attachments.length > 0) {
4369
4320
  updateThread(threadId, { attachments: [] });
4370
4321
  }
@@ -4388,11 +4339,10 @@ var Orchestrator = class {
4388
4339
  });
4389
4340
  const artifactDirective = isBrightsy ? null : formatArtifactDirective();
4390
4341
  const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
4391
- const { autoRenameBranchEnabled } = await import("../app-settings-LTSFG7QJ.js");
4342
+ const { autoRenameBranchEnabled } = await import("../app-settings-GJVZGU3M.js");
4392
4343
  const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled() ? formatRenameBranchDirective(fresh, {
4393
4344
  customPrompt: settings?.prompts?.renameBranch
4394
4345
  }) : null;
4395
- const instructions = fresh.agent === "claude" || isBrightsy ? null : formatAgentInstructions(instructionFiles);
4396
4346
  let seed = null;
4397
4347
  if (!fresh.sessionId) {
4398
4348
  const prior = fresh.messages.slice(0, -1);
@@ -4413,12 +4363,12 @@ var Orchestrator = class {
4413
4363
  });
4414
4364
  }
4415
4365
  }
4416
- const cachedPrefix = [
4366
+ const cachedPrefix = fresh.sessionId ? "" : [
4417
4367
  coordinatorDirective,
4418
4368
  worktreeDirective,
4419
4369
  artifactDirective,
4420
4370
  renameBranchDirective,
4421
- ...fresh.agent === "claude" && fresh.sessionId ? [] : [instructions, seed]
4371
+ seed
4422
4372
  ].filter(Boolean).join("\n\n---\n\n");
4423
4373
  try {
4424
4374
  const stderrTail = [];
@@ -4505,15 +4455,11 @@ var Orchestrator = class {
4505
4455
  const retryThread = this.requireThread(threadId);
4506
4456
  const prior = retryThread.messages.slice(0, -1);
4507
4457
  const retrySeed = buildSessionSeed(prior);
4508
- const retryInstructions = retryThread.agent === "claude" ? null : formatAgentInstructions(
4509
- loadAgentInstructions(retryThread.worktreePath, retryThread.agent)
4510
- );
4511
4458
  const retryPrefix = [
4512
4459
  coordinatorDirective,
4513
4460
  worktreeDirective,
4514
4461
  artifactDirective,
4515
4462
  renameBranchDirective,
4516
- retryInstructions,
4517
4463
  retrySeed
4518
4464
  ].filter(Boolean).join("\n\n---\n\n");
4519
4465
  const retryHandle = await spawnAgentTurn(
@@ -5083,7 +5029,7 @@ var Orchestrator = class {
5083
5029
  updateThread(thread.id, { title: meta.title });
5084
5030
  }
5085
5031
  }
5086
- const { autoArchiveOnMergeEnabled } = await import("../app-settings-LTSFG7QJ.js");
5032
+ const { autoArchiveOnMergeEnabled } = await import("../app-settings-GJVZGU3M.js");
5087
5033
  const latest = this.requireThread(thread.id);
5088
5034
  if (!shouldAutoArchiveOnPrMerge({
5089
5035
  previousPrState: prevState || null,
@@ -5307,7 +5253,7 @@ var Orchestrator = class {
5307
5253
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
5308
5254
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
5309
5255
  try {
5310
- const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-E75POJXP.js");
5256
+ const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-AIUYUXB7.js");
5311
5257
  await ensureWorkspace2(thread.repoPath);
5312
5258
  } catch {
5313
5259
  }
@@ -5328,7 +5274,7 @@ var Orchestrator = class {
5328
5274
  await runArchiveScript(thread.repoPath, thread.worktreePath);
5329
5275
  } catch {
5330
5276
  }
5331
- const { deleteBranchOnPurgeEnabled } = await import("../app-settings-LTSFG7QJ.js");
5277
+ const { deleteBranchOnPurgeEnabled } = await import("../app-settings-GJVZGU3M.js");
5332
5278
  const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled();
5333
5279
  await removeWorktree(thread.repoPath, thread.worktreePath, {
5334
5280
  deleteBranch: deleteBranch ? thread.branchName : void 0
@@ -5349,7 +5295,7 @@ var Orchestrator = class {
5349
5295
  return restored2;
5350
5296
  }
5351
5297
  if (!existsSync13(thread.worktreePath)) {
5352
- const { createThreadWorktree: createThreadWorktree2 } = await import("../worktree-LZEGVQSN.js");
5298
+ const { createThreadWorktree: createThreadWorktree2 } = await import("../worktree-WMDJW2BL.js");
5353
5299
  const { execa: execa6 } = await import("execa");
5354
5300
  const slug = thread.worktreePath.split("/").pop();
5355
5301
  const dest = thread.worktreePath;
@@ -5565,7 +5511,7 @@ function withTimeout(promise, ms, label) {
5565
5511
  async function startMcpServer() {
5566
5512
  const orch = getOrchestrator();
5567
5513
  try {
5568
- const { maxConcurrentAgents } = await import("../app-settings-LTSFG7QJ.js");
5514
+ const { maxConcurrentAgents } = await import("../app-settings-GJVZGU3M.js");
5569
5515
  orch.setMaxConcurrent(maxConcurrentAgents());
5570
5516
  } catch {
5571
5517
  }
@@ -5656,14 +5602,13 @@ async function startMcpServer() {
5656
5602
  ),
5657
5603
  artifact_id: z.string().optional().describe("Stable id when updating the same artifact across turns")
5658
5604
  },
5659
- async ({ title, type, content, artifact_id }) => {
5605
+ async ({ title, type, artifact_id }) => {
5660
5606
  const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
5661
5607
  const payload = {
5662
5608
  ok: true,
5663
5609
  artifact_id: id,
5664
5610
  title,
5665
5611
  type,
5666
- content,
5667
5612
  message: "Artifact accepted. Sideboard desktop opens it in the side column beside chat."
5668
5613
  };
5669
5614
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
@@ -5716,7 +5661,6 @@ async function startMcpServer() {
5716
5661
  ok: true,
5717
5662
  path,
5718
5663
  title: title?.trim() || "Plan",
5719
- content,
5720
5664
  message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
5721
5665
  };
5722
5666
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
@@ -5746,9 +5690,6 @@ async function startMcpServer() {
5746
5690
  datasource: args.datasource ?? (args.resource ? "inline" : "brightsy"),
5747
5691
  resource_id: args.resource_id,
5748
5692
  record_id: args.record_id,
5749
- resource: args.resource,
5750
- record: args.record,
5751
- records: args.records,
5752
5693
  message: "Schema pane accepted. Sideboard desktop opens the CMS column beside chat."
5753
5694
  };
5754
5695
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
@@ -5776,739 +5717,741 @@ async function startMcpServer() {
5776
5717
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
5777
5718
  }
5778
5719
  );
5779
- const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults } = await import("../app-settings-LTSFG7QJ.js");
5780
- const accountDefaults = resolveThreadDefaults();
5781
- const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
5782
- server.tool(
5783
- "create_thread",
5784
- `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.`,
5785
- {
5786
- sourceType: z.enum(["branch", "pr", "ticket"]),
5787
- sourceRef: z.string(),
5788
- agent: z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
5789
- model: z.string().nullable().optional().describe(
5790
- `Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
5791
- ),
5792
- repoPath: z.string(),
5793
- title: z.string().optional(),
5794
- parentThreadId: z.string().optional().describe(
5795
- "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
5796
- )
5797
- },
5798
- async (args) => {
5799
- const envParentId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || "";
5800
- let parentId = args.parentThreadId?.trim() || "";
5801
- let parent = parentId ? orch.getThread(parentId) : null;
5802
- let parentCorrectedFrom;
5803
- if (envParentId) {
5804
- const envParent = orch.getThread(envParentId);
5805
- if (envParent) {
5806
- if (parentId && parentId !== envParentId) parentCorrectedFrom = parentId;
5807
- else if (!parentId) parentCorrectedFrom = void 0;
5808
- parentId = envParentId;
5809
- parent = envParent;
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
+ }
5810
5753
  }
5811
- }
5812
- if (parentId && !parent) {
5813
- parentCorrectedFrom = parentId;
5814
- parentId = "";
5815
- parent = null;
5816
- }
5817
- if (parentId) {
5818
- const children = orch.getThreads(false).filter((t) => t.parentThreadId === parentId);
5819
- if (children.length >= MAX_ORCH_THREADS) {
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);
5820
5827
  return {
5821
5828
  content: [
5822
5829
  {
5823
5830
  type: "text",
5824
- text: `Thread-creation cap (${MAX_ORCH_THREADS}) reached for this orchestration session`
5831
+ text: `create_thread failed: ${message}`
5825
5832
  }
5826
5833
  ],
5827
5834
  isError: true
5828
5835
  };
5829
5836
  }
5830
5837
  }
5831
- let agentArg = args.agent;
5832
- let agentCoercedFrom;
5833
- const resolvedProbe = resolveNewThreadOptions2({ agent: agentArg }).agent;
5834
- if (resolvedProbe === "codex") {
5835
- agentCoercedFrom = agentArg ?? "codex";
5836
- const accountAgent = resolveNewThreadOptions2({}).agent;
5837
- agentArg = accountAgent !== "codex" ? accountAgent : "cursor";
5838
- }
5839
- const opts = resolveNewThreadOptions2({
5840
- agent: agentArg,
5841
- model: args.model
5842
- });
5843
- try {
5844
- const thread = await withTimeout(
5845
- orch.createThread({
5846
- sourceType: args.sourceType,
5847
- sourceRef: args.sourceRef,
5848
- agent: opts.agent,
5849
- model: opts.model,
5850
- effort: opts.effort,
5851
- fast: opts.fast,
5852
- repoPath: args.repoPath,
5853
- title: args.title,
5854
- parentThreadId: parentId || null
5855
- }),
5856
- CREATE_THREAD_TIMEOUT_MS,
5857
- "create_thread"
5858
- );
5859
- 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);
5860
5855
  return {
5861
5856
  content: [
5862
5857
  {
5863
5858
  type: "text",
5864
5859
  text: JSON.stringify({
5865
5860
  id: thread.id,
5866
- title: thread.title,
5867
- branchName: thread.branchName,
5868
- worktreePath: thread.worktreePath,
5869
- agent: thread.agent,
5870
- model: thread.model,
5871
5861
  status: thread.status,
5872
- link: `sideboard://thread/${thread.id}`,
5873
- parentThreadId: thread.parentThreadId,
5874
- ...agentCoercedFrom ? {
5875
- agentCoercedFrom,
5876
- note: `Avoid nested Codex under a Codex orchestrator \u2014 used Account default agent=${thread.agent}`
5877
- } : {},
5878
- ...parentCorrectedFrom ? { parentCorrectedFrom, parentNote } : {}
5862
+ queueLength: thread.queue.length,
5863
+ forceStopped: Boolean(force_stop)
5879
5864
  })
5880
5865
  }
5881
5866
  ]
5882
5867
  };
5883
- } catch (err) {
5884
- const message = err instanceof Error ? err.message : String(err);
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);
5885
5880
  return {
5886
5881
  content: [
5887
5882
  {
5888
5883
  type: "text",
5889
- text: `create_thread failed: ${message}`
5884
+ text: JSON.stringify({
5885
+ id: thread.id,
5886
+ status: result.status,
5887
+ text: result.text
5888
+ })
5890
5889
  }
5891
- ],
5892
- isError: true
5890
+ ]
5893
5891
  };
5894
5892
  }
5895
- }
5896
- );
5897
- server.tool(
5898
- "send_to_thread",
5899
- "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.",
5900
- {
5901
- ref: z.string(),
5902
- prompt: z.string(),
5903
- force_stop: z.boolean().optional()
5904
- },
5905
- async ({ ref, prompt, force_stop }) => {
5906
- if (force_stop) {
5907
- const existing = orch.getThread(ref);
5908
- if (existing) {
5909
- orch.stop(ref, { clearQueue: true });
5910
- }
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) }] };
5911
5901
  }
5912
- const thread = await orch.send(ref, prompt);
5913
- return {
5914
- content: [
5915
- {
5916
- type: "text",
5917
- text: JSON.stringify({
5918
- id: thread.id,
5919
- status: thread.status,
5920
- queueLength: thread.queue.length,
5921
- forceStopped: Boolean(force_stop)
5922
- })
5923
- }
5924
- ]
5925
- };
5926
- }
5927
- );
5928
- server.tool(
5929
- "wait_for_turn",
5930
- "Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread to read the agent reply.",
5931
- {
5932
- ref: z.string(),
5933
- timeoutMs: z.number().optional()
5934
- },
5935
- async ({ ref, timeoutMs }) => {
5936
- const thread = await orch.waitForTurn(ref, timeoutMs ?? 6e5);
5937
- const result = orch.getTurnResult(thread.id);
5938
- return {
5939
- content: [
5940
- {
5941
- type: "text",
5942
- text: JSON.stringify({
5943
- id: thread.id,
5944
- status: result.status,
5945
- text: result.text
5946
- })
5947
- }
5948
- ]
5949
- };
5950
- }
5951
- );
5952
- server.tool(
5953
- "get_turn_result",
5954
- "Final assistant message only (not full transcript)",
5955
- { ref: z.string() },
5956
- async ({ ref }) => {
5957
- const result = orch.getTurnResult(ref);
5958
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
5959
- }
5960
- );
5961
- server.tool(
5962
- "stop_thread",
5963
- "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.",
5964
- {
5965
- ref: z.string(),
5966
- force: z.boolean().optional()
5967
- },
5968
- async ({ ref, force }) => {
5969
- const t = orch.getThread(ref);
5970
- 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 });
5971
5921
  return {
5972
- content: [{ type: "text", text: `Thread not found: ${ref}` }],
5973
- isError: true
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
+ ]
5974
5932
  };
5975
5933
  }
5976
- const clearQueue = force !== false;
5977
- const hadQueued = t.queue.length > 0;
5978
- const stopped = orch.stop(ref, { clearQueue });
5979
- return {
5980
- content: [
5981
- {
5982
- type: "text",
5983
- text: JSON.stringify({
5984
- id: stopped.id,
5985
- status: stopped.status,
5986
- clearedQueue: clearQueue && hadQueued
5987
- })
5988
- }
5989
- ]
5990
- };
5991
- }
5992
- );
5993
- server.tool(
5994
- "archive_thread",
5995
- "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.",
5996
- { ref: z.string() },
5997
- async ({ ref }) => {
5998
- const t = orch.getThread(ref);
5999
- 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);
6000
5955
  return {
6001
- content: [{ type: "text", text: `Thread not found: ${ref}` }],
6002
- isError: true
5956
+ content: [
5957
+ {
5958
+ type: "text",
5959
+ text: JSON.stringify({
5960
+ id: archived.id,
5961
+ status: archived.status
5962
+ })
5963
+ }
5964
+ ]
6003
5965
  };
6004
5966
  }
6005
- const blocked = mcpArchiveBlockedReason(t);
6006
- if (blocked) {
6007
- return {
6008
- content: [{ type: "text", text: blocked }],
6009
- isError: true
6010
- };
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
+ }
6011
5991
  }
6012
- const archived = await orch.archive(ref);
6013
- return {
6014
- content: [
6015
- {
6016
- type: "text",
6017
- text: JSON.stringify({
6018
- id: archived.id,
6019
- status: archived.status
6020
- })
6021
- }
6022
- ]
6023
- };
6024
- }
6025
- );
6026
- server.tool(
6027
- "restore_thread",
6028
- "Restore an archived thread (recreates worktree from branch when needed)",
6029
- { ref: z.string() },
6030
- async ({ ref }) => {
6031
- try {
6032
- 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);
6033
6002
  return {
6034
6003
  content: [
6035
6004
  {
6036
6005
  type: "text",
6037
6006
  text: JSON.stringify({
6038
- id: restored.id,
6039
- status: restored.status,
6040
- worktreePath: restored.worktreePath
6007
+ ...summary,
6008
+ files: summary.files.slice(0, maxFiles ?? 10)
6041
6009
  })
6042
6010
  }
6043
6011
  ]
6044
6012
  };
6045
- } catch (err) {
6046
- const message = err instanceof Error ? err.message : String(err);
6047
- return { content: [{ type: "text", text: message }], isError: true };
6048
6013
  }
6049
- }
6050
- );
6051
- server.tool(
6052
- "get_diff",
6053
- "Compact diff summary (capped hunks, paginated)",
6054
- {
6055
- ref: z.string(),
6056
- maxFiles: z.number().optional()
6057
- },
6058
- async ({ ref, maxFiles }) => {
6059
- const summary = await orch.diffSummary(ref);
6060
- return {
6061
- content: [
6062
- {
6063
- type: "text",
6064
- text: JSON.stringify({
6065
- ...summary,
6066
- files: summary.files.slice(0, maxFiles ?? 10)
6067
- })
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
+ };
6068
6125
  }
6069
- ]
6070
- };
6071
- }
6072
- );
6073
- server.tool(
6074
- "request_review",
6075
- '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.',
6076
- { ref: z.string().describe("Worktree thread id/ref to review") },
6077
- async ({ ref }) => {
6078
- try {
6079
- const tab = await orch.requestReview(ref);
6080
- const from = orch.getThread(ref);
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);
6081
6166
  return {
6082
6167
  content: [
6083
6168
  {
6084
6169
  type: "text",
6085
6170
  text: JSON.stringify({
6086
- id: tab.id,
6087
- title: tab.title,
6088
- status: tab.status,
6089
- fromThreadId: from?.id ?? ref,
6090
- link: `sideboard://thread/${tab.id}`
6171
+ port: result.port,
6172
+ scriptName: result.scriptName,
6173
+ ports: result.ports,
6174
+ url: `http://localhost:${result.port}`
6091
6175
  })
6092
6176
  }
6093
6177
  ]
6094
6178
  };
6095
- } catch (err) {
6096
- const message = err instanceof Error ? err.message : String(err);
6097
- return { content: [{ type: "text", text: message }], isError: true };
6098
6179
  }
6099
- }
6100
- );
6101
- const agentEnum = z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
6102
- server.tool(
6103
- "list_models",
6104
- "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.",
6105
- {
6106
- agent: agentEnum.optional().describe("Limit to one agent; omit for all")
6107
- },
6108
- async ({ agent }) => {
6109
- try {
6110
- 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);
6111
6188
  return {
6112
- content: [{ type: "text", text: JSON.stringify(catalogs, null, 2) }]
6189
+ content: [
6190
+ {
6191
+ type: "text",
6192
+ text: JSON.stringify({ scripts, active }, null, 2)
6193
+ }
6194
+ ]
6113
6195
  };
6114
- } catch (err) {
6115
- const message = err instanceof Error ? err.message : String(err);
6116
- return { content: [{ type: "text", text: message }], isError: true };
6117
6196
  }
6118
- }
6119
- );
6120
- server.tool(
6121
- "fork_worktree",
6122
- "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.",
6123
- {
6124
- ref: z.string().describe("Worktree thread id/ref to fork"),
6125
- through_index: z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
6126
- agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
6127
- model: z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
6128
- title: z.string().optional()
6129
- },
6130
- async ({ ref, through_index, agent, model, title }) => {
6131
- try {
6132
- const source = orch.getThread(ref);
6133
- if (source) await orch.reconcile(source.repoPath, { drainQueues: false });
6134
- const thread = await orch.forkThreadWorktree({
6135
- threadId: ref,
6136
- throughIndex: through_index,
6137
- agent,
6138
- model,
6139
- title
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
6140
6282
  });
6141
6283
  return {
6142
6284
  content: [
6143
6285
  {
6144
6286
  type: "text",
6145
- text: JSON.stringify({
6146
- id: thread.id,
6147
- title: thread.title,
6148
- status: thread.status,
6149
- agent: thread.agent,
6150
- model: thread.model,
6151
- branchName: thread.branchName,
6152
- worktreePath: thread.worktreePath,
6153
- fromThreadId: source?.id ?? ref,
6154
- link: `sideboard://thread/${thread.id}`
6155
- })
6287
+ text: branches.map((b) => `${b.current ? "*" : " "} ${b.name}`).join("\n")
6156
6288
  }
6157
6289
  ]
6158
6290
  };
6159
- } catch (err) {
6160
- const message = err instanceof Error ? err.message : String(err);
6161
- return { content: [{ type: "text", text: message }], isError: true };
6162
6291
  }
6163
- }
6164
- );
6165
- server.tool(
6166
- "fork_chat",
6167
- "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.",
6168
- {
6169
- ref: z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
6170
- through_index: z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
6171
- agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
6172
- model: z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
6173
- title: z.string().optional()
6174
- },
6175
- async ({ ref, through_index, agent, model, title }) => {
6176
- try {
6177
- const source = orch.getThread(ref);
6178
- if (!source) {
6179
- return {
6180
- content: [{ type: "text", text: `Thread not found: ${ref}` }],
6181
- isError: true
6182
- };
6183
- }
6184
- const tab = orch.forkChatTab({
6185
- threadId: source.id,
6186
- throughIndex: through_index,
6187
- agent,
6188
- model,
6189
- title
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
6190
6406
  });
6191
6407
  return {
6192
6408
  content: [
6193
6409
  {
6194
6410
  type: "text",
6195
- text: JSON.stringify({
6196
- id: tab.id,
6197
- title: tab.title,
6198
- status: tab.status,
6199
- agent: tab.agent,
6200
- model: tab.model,
6201
- sourceType: tab.sourceType,
6202
- worktreePath: tab.worktreePath,
6203
- fromThreadId: source.id,
6204
- link: `sideboard://thread/${tab.id}`
6205
- })
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
+ )
6206
6427
  }
6207
6428
  ]
6208
6429
  };
6209
- } catch (err) {
6210
- const message = err instanceof Error ? err.message : String(err);
6211
- return { content: [{ type: "text", text: message }], isError: true };
6212
6430
  }
6213
- }
6214
- );
6215
- server.tool(
6216
- "run_dev_script",
6217
- "Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
6218
- {
6219
- ref: z.string(),
6220
- name: z.string().optional()
6221
- },
6222
- async ({ ref, name }) => {
6223
- const result = await orch.startDev(ref, name);
6224
- return {
6225
- content: [
6226
- {
6227
- type: "text",
6228
- text: JSON.stringify({
6229
- port: result.port,
6230
- scriptName: result.scriptName,
6231
- ports: result.ports,
6232
- url: `http://localhost:${result.port}`
6233
- })
6234
- }
6235
- ]
6236
- };
6237
- }
6238
- );
6239
- server.tool(
6240
- "list_run_scripts",
6241
- "List named run scripts available for a thread",
6242
- { ref: z.string() },
6243
- async ({ ref }) => {
6244
- const scripts = orch.listThreadRunScripts(ref);
6245
- const active = orch.getActiveRuns(ref);
6246
- return {
6247
- content: [
6248
- {
6249
- type: "text",
6250
- text: JSON.stringify({ scripts, active }, null, 2)
6251
- }
6252
- ]
6253
- };
6254
- }
6255
- );
6256
- server.tool(
6257
- "stop_dev_script",
6258
- "Stop a running script for a thread (all scripts if name omitted)",
6259
- {
6260
- ref: z.string(),
6261
- name: z.string().optional()
6262
- },
6263
- async ({ ref, name }) => {
6264
- orch.stopDev(ref, name);
6265
- return { content: [{ type: "text", text: "ok" }] };
6266
- }
6267
- );
6268
- server.tool(
6269
- "run_setup",
6270
- "Re-run workspace setup (Sideboard/Conductor settings or .cursor/worktrees.json)",
6271
- { ref: z.string() },
6272
- async ({ ref }) => {
6273
- const result = await orch.runSetup(ref);
6274
- return {
6275
- content: [{ type: "text", text: JSON.stringify(result) }]
6276
- };
6277
- }
6278
- );
6279
- server.tool(
6280
- "add_workspace",
6281
- "Register a git repo as a Sideboard workspace",
6282
- { repoPath: z.string() },
6283
- async ({ repoPath }) => {
6284
- const ws = await orch.addWorkspace(repoPath);
6285
- return { content: [{ type: "text", text: JSON.stringify(ws) }] };
6286
- }
6287
- );
6288
- server.tool(
6289
- "remove_workspace",
6290
- "Unregister a Sideboard workspace (does not archive threads)",
6291
- { repoPath: z.string() },
6292
- async ({ repoPath }) => {
6293
- orch.removeWorkspace(repoPath);
6294
- return { content: [{ type: "text", text: "ok" }] };
6295
- }
6296
- );
6297
- server.tool(
6298
- "fanout",
6299
- "Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
6300
- {
6301
- prompt: z.string(),
6302
- agents: z.array(z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
6303
- repoPath: z.string(),
6304
- sourceType: z.enum(["branch", "pr", "ticket"]).optional(),
6305
- sourceRef: z.string().optional(),
6306
- title: z.string().optional()
6307
- },
6308
- async (args) => {
6309
- const threads = await orch.bestOfN(args);
6310
- return {
6311
- content: [
6312
- {
6313
- type: "text",
6314
- text: JSON.stringify(
6315
- threads.map((t) => ({
6316
- id: t.id,
6317
- agent: t.agent,
6318
- branchName: t.branchName,
6319
- worktreePath: t.worktreePath
6320
- })),
6321
- null,
6322
- 2
6323
- )
6324
- }
6325
- ]
6326
- };
6327
- }
6328
- );
6329
- server.tool(
6330
- "list_branches",
6331
- "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).",
6332
- {
6333
- repoPath: z.string(),
6334
- unmergedOnly: z.boolean().optional()
6335
- },
6336
- async ({ repoPath, unmergedOnly }) => {
6337
- const root = await resolveRepoRoot(repoPath);
6338
- const branches = await listBranches(root, {
6339
- unmergedOnly: unmergedOnly !== false
6340
- });
6341
- return {
6342
- content: [
6343
- {
6344
- type: "text",
6345
- text: branches.map((b) => `${b.current ? "*" : " "} ${b.name}`).join("\n")
6346
- }
6347
- ]
6348
- };
6349
- }
6350
- );
6351
- server.tool(
6352
- "list_prs",
6353
- "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.",
6354
- { repoPath: z.string() },
6355
- async ({ repoPath }) => {
6356
- const root = await resolveRepoRoot(repoPath);
6357
- const prs = await listPrs(root);
6358
- return {
6359
- content: [
6360
- {
6361
- type: "text",
6362
- text: prs.map(
6363
- (p) => `#${p.number} ${p.title} (${p.headRefName})${p.isCrossRepository ? " [fork]" : ""}`
6364
- ).join("\n")
6365
- }
6366
- ]
6367
- };
6368
- }
6369
- );
6370
- server.tool(
6371
- "get_pr_stack",
6372
- "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.",
6373
- { ref: z.string() },
6374
- async ({ ref }) => {
6375
- const stack = await orch.getPrStack(ref);
6376
- return {
6377
- content: [{ type: "text", text: JSON.stringify(stack, null, 2) }]
6378
- };
6379
- }
6380
- );
6381
- server.tool(
6382
- "open_pr_stack_layers",
6383
- "Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
6384
- {
6385
- ref: z.string(),
6386
- layer: z.number().int().positive().optional()
6387
- },
6388
- async ({ ref, layer }) => {
6389
- const result = await orch.openPrStackLayers(ref, { layer });
6390
- return {
6391
- content: [
6392
- {
6393
- type: "text",
6394
- text: JSON.stringify(
6395
- {
6396
- stackNumber: result.stack.stackNumber,
6397
- trunk: result.stack.trunk,
6398
- threads: result.threads.map((t) => ({
6399
- id: t.id,
6400
- title: t.title,
6401
- branchName: t.branchName,
6402
- stackLayer: t.stackLayer,
6403
- worktreePath: t.worktreePath,
6404
- prUrl: t.prUrl,
6405
- link: `sideboard://thread/${t.id}`
6406
- }))
6407
- },
6408
- null,
6409
- 2
6410
- )
6411
- }
6412
- ]
6413
- };
6414
- }
6415
- );
6416
- server.tool(
6417
- "add_stack_layer",
6418
- "Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
6419
- {
6420
- ref: z.string(),
6421
- branchName: z.string(),
6422
- title: z.string().optional()
6423
- },
6424
- async ({ ref, branchName, title }) => {
6425
- const result = await orch.addStackLayer(ref, branchName, { title });
6426
- return {
6427
- content: [
6428
- {
6429
- type: "text",
6430
- text: JSON.stringify(
6431
- {
6432
- id: result.thread.id,
6433
- title: result.thread.title,
6434
- branchName: result.thread.branchName,
6435
- stackLayer: result.thread.stackLayer,
6436
- worktreePath: result.thread.worktreePath,
6437
- link: `sideboard://thread/${result.thread.id}`
6438
- },
6439
- null,
6440
- 2
6441
- )
6442
- }
6443
- ]
6444
- };
6445
- }
6446
- );
6447
- server.tool(
6448
- "create_pr_stack",
6449
- "Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
6450
- {
6451
- repoPath: z.string(),
6452
- branches: z.array(z.string()).min(1),
6453
- agent: z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
6454
- base: z.string().optional(),
6455
- title: z.string().optional()
6456
- },
6457
- async (args) => {
6458
- const result = await orch.createPrStack({
6459
- repoPath: args.repoPath,
6460
- branches: args.branches,
6461
- agent: args.agent,
6462
- base: args.base,
6463
- title: args.title
6464
- });
6465
- return {
6466
- content: [
6467
- {
6468
- type: "text",
6469
- text: JSON.stringify(
6470
- {
6471
- stackNumber: result.stack.stackNumber,
6472
- trunk: result.stack.trunk,
6473
- threads: result.threads.map((t) => ({
6474
- id: t.id,
6475
- title: t.title,
6476
- branchName: t.branchName,
6477
- stackLayer: t.stackLayer,
6478
- worktreePath: t.worktreePath,
6479
- link: `sideboard://thread/${t.id}`
6480
- }))
6481
- },
6482
- null,
6483
- 2
6484
- )
6485
- }
6486
- ]
6487
- };
6488
- }
6489
- );
6490
- server.tool(
6491
- "list_issues",
6492
- "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.",
6493
- { repoPath: z.string() },
6494
- async ({ repoPath }) => {
6495
- const root = await resolveRepoRoot(repoPath);
6496
- const result = await listIssues(root);
6497
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
6498
- }
6499
- );
6500
- server.tool(
6501
- "list_linear_issues",
6502
- "Deprecated: prefer list_issues. Lists assigned Linear issues via the chosen agent MCP connector",
6503
- {
6504
- agent: z.enum(["claude", "codex", "opencode"]),
6505
- repoPath: z.string()
6506
- },
6507
- async ({ agent, repoPath }) => {
6508
- const issues = await listLinearIssues(agent, repoPath);
6509
- return { content: [{ type: "text", text: JSON.stringify(issues, null, 2) }] };
6510
- }
6511
- );
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
+ }
6512
6455
  const transport = new StdioServerTransport();
6513
6456
  await server.connect(transport);
6514
6457
  }