@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.
Files changed (45) hide show
  1. package/dist/{agents-B2723EOF.js → agents-EWFD765A.js} +23 -7
  2. package/dist/{agents-EADRAAPG.js → agents-MD7SCFYX.js} +23 -7
  3. package/dist/{app-settings-YBNZ4XSR.js → app-settings-GJVZGU3M.js} +15 -3
  4. package/dist/{app-settings-TBHTJWLQ.js → app-settings-ONKMFSEJ.js} +15 -3
  5. package/dist/{chunk-RIWUU7FO.js → chunk-5AG5BUQN.js} +61 -3
  6. package/dist/{chunk-HLEX5AQ6.js → chunk-7FD7COKE.js} +4 -0
  7. package/dist/{chunk-XWPKLRB3.js → chunk-AKPEXIKK.js} +2 -2
  8. package/dist/{chunk-MX7UH5AT.js → chunk-BBEH2CJE.js} +264 -93
  9. package/dist/{chunk-FGNBTFRD.js → chunk-BPOTVI5J.js} +125 -3
  10. package/dist/{chunk-CHGOMFTA.js → chunk-DNPP7QMK.js} +32 -7
  11. package/dist/{chunk-MBAL5FE6.js → chunk-DZYMNW2A.js} +4 -4
  12. package/dist/{chunk-JB2H7E7V.js → chunk-EVD4KNKR.js} +1 -1
  13. package/dist/{chunk-TSRXOSVD.js → chunk-FWJYLBO6.js} +4 -0
  14. package/dist/{chunk-QJBC4BRK.js → chunk-HTOTTJJX.js} +2 -2
  15. package/dist/{chunk-MFGGIRUA.js → chunk-I2KGRAXC.js} +2 -2
  16. package/dist/{chunk-ZXDOI6IH.js → chunk-IFAU2DEY.js} +269 -93
  17. package/dist/{chunk-NTYRBRE7.js → chunk-LRBRQFBU.js} +2 -2
  18. package/dist/{chunk-75QR74KM.js → chunk-MHR4OGZ4.js} +125 -3
  19. package/dist/{chunk-2P2UK7E7.js → chunk-PESJDZTB.js} +2 -2
  20. package/dist/{chunk-P7EQUFB6.js → chunk-R7GUDEPY.js} +2 -2
  21. package/dist/{chunk-73AT5K4A.js → chunk-TXQ6K2GR.js} +61 -3
  22. package/dist/{chunk-MLAST5BB.js → chunk-YTNLOJPK.js} +32 -7
  23. package/dist/{connected-teams-RSL4VB3X.js → connected-teams-CHXO5FFA.js} +2 -2
  24. package/dist/{connected-teams-MKIYKOYA.js → connected-teams-GJTESSTX.js} +2 -2
  25. package/dist/{coordinator-prompt-4P23VVFC.js → coordinator-prompt-E5UGSJES.js} +5 -5
  26. package/dist/{coordinator-prompt-LNWHZ64D.js → coordinator-prompt-ILSULI7C.js} +5 -5
  27. package/dist/{global-workspace-TMMAVRVW.js → global-workspace-44UG64NA.js} +6 -7
  28. package/dist/{global-workspace-BHT4XUAA.js → global-workspace-5PM5LA6E.js} +6 -7
  29. package/dist/index.cjs +1433 -944
  30. package/dist/index.d.cts +117 -2
  31. package/dist/index.d.ts +117 -2
  32. package/dist/index.js +859 -736
  33. package/dist/mcp/run-stdio.cjs +1388 -993
  34. package/dist/mcp/run-stdio.js +798 -765
  35. package/dist/{run-HZTBWGQR.js → run-74S2NZWB.js} +1 -1
  36. package/dist/{run-GPSVV5OH.js → run-JLN5JK7Q.js} +1 -1
  37. package/dist/{thread-store-GHOADGL2.js → thread-store-BUBR24YA.js} +1 -1
  38. package/dist/{thread-store-UJIGMI5J.js → thread-store-XTPFCJAI.js} +1 -1
  39. package/dist/{workspaces-3GO3EDQG.js → workspaces-OVBCSS5Y.js} +7 -8
  40. package/dist/{workspaces-GFPGDGCS.js → workspaces-RXFXQRRF.js} +7 -8
  41. package/dist/{worktree-GBLHEFHR.js → worktree-K2JB53R5.js} +3 -3
  42. package/dist/{worktree-3L2G6PCM.js → worktree-S2N63FLV.js} +3 -3
  43. package/package.json +1 -1
  44. package/dist/chunk-GI7E5733.js +0 -30
  45. package/dist/chunk-S2LL5HA4.js +0 -33
@@ -486,6 +486,8 @@ function normalizeThread(raw) {
486
486
  agentPid: raw.agentPid ?? null,
487
487
  attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
488
488
  prTitle: raw.prTitle ?? null,
489
+ prState: raw.prState ?? null,
490
+ skipAutoArchiveOnMerge: Boolean(raw.skipAutoArchiveOnMerge),
489
491
  stackId: raw.stackId ?? null,
490
492
  stackLayer: raw.stackLayer ?? null,
491
493
  userSetTitle: Boolean(raw.userSetTitle),
@@ -512,6 +514,8 @@ function createEmptyThread(partial) {
512
514
  activeRuns: partial.activeRuns ?? [],
513
515
  prUrl: partial.prUrl ?? null,
514
516
  prTitle: partial.prTitle ?? null,
517
+ prState: partial.prState ?? null,
518
+ skipAutoArchiveOnMerge: partial.skipAutoArchiveOnMerge ?? false,
515
519
  stackId: partial.stackId ?? null,
516
520
  stackLayer: partial.stackLayer ?? null,
517
521
  userSetTitle: partial.userSetTitle ?? false,
@@ -1503,18 +1507,28 @@ var init_gh_errors = __esm({
1503
1507
  });
1504
1508
 
1505
1509
  // src/agents/path.ts
1510
+ function prependPathDir(env, dir) {
1511
+ if (!dir || !(0, import_node_fs4.existsSync)(dir)) return;
1512
+ const current = env.PATH ?? "";
1513
+ const parts = current.split(import_node_path4.delimiter).filter(Boolean);
1514
+ if (parts.includes(dir)) {
1515
+ env.PATH = current;
1516
+ return;
1517
+ }
1518
+ env.PATH = [dir, ...parts].join(import_node_path4.delimiter);
1519
+ }
1506
1520
  function ensureAgentPath(env = process.env) {
1507
1521
  const home = env.HOME || env.USERPROFILE || (0, import_node_os3.homedir)();
1508
1522
  const current = env.PATH ?? "";
1509
1523
  const parts = current.split(import_node_path4.delimiter).filter(Boolean);
1510
1524
  const seen = new Set(parts);
1511
1525
  const extras = [
1512
- ...EXTRA_BIN_DIRS.map((rel) => `${home}/${rel}`),
1526
+ ...EXTRA_BIN_DIRS.map((rel) => (0, import_node_path4.join)(home, rel)),
1513
1527
  "/opt/homebrew/bin",
1514
1528
  "/usr/local/bin"
1515
1529
  ];
1516
1530
  for (const dir of extras.reverse()) {
1517
- if (!dir || seen.has(dir)) continue;
1531
+ if (!dir || seen.has(dir) || !(0, import_node_fs4.existsSync)(dir)) continue;
1518
1532
  parts.unshift(dir);
1519
1533
  seen.add(dir);
1520
1534
  }
@@ -1522,10 +1536,50 @@ function ensureAgentPath(env = process.env) {
1522
1536
  env.PATH = next;
1523
1537
  return next;
1524
1538
  }
1525
- var import_node_os3, import_node_path4, EXTRA_BIN_DIRS;
1539
+ function enrichPathWithNpmGlobalBin(env = process.env) {
1540
+ ensureAgentPath(env);
1541
+ try {
1542
+ const prefix = (0, import_node_child_process.execFileSync)("npm", ["prefix", "-g"], {
1543
+ encoding: "utf8",
1544
+ env: { ...process.env, ...env },
1545
+ timeout: 8e3,
1546
+ stdio: ["ignore", "pipe", "ignore"]
1547
+ }).trim().split(/\r?\n/).find(Boolean);
1548
+ if (prefix) {
1549
+ const binDir = process.platform === "win32" ? prefix : (0, import_node_path4.join)(prefix, "bin");
1550
+ prependPathDir(env, binDir);
1551
+ }
1552
+ } catch {
1553
+ }
1554
+ return env.PATH ?? "";
1555
+ }
1556
+ function posixShellSingleQuote(value) {
1557
+ return `'${value.replace(/'/g, `'\\''`)}'`;
1558
+ }
1559
+ function resolveCommandBinarySync(command, whichPath) {
1560
+ const trimmed = command.trim();
1561
+ if (!trimmed) return trimmed;
1562
+ const match = /^(\S+)(\s[\s\S]*)?$/.exec(trimmed);
1563
+ if (!match) return trimmed;
1564
+ const bin = match[1];
1565
+ const rest = match[2] ?? "";
1566
+ if (bin.includes("/") || bin.includes("\\")) return trimmed;
1567
+ const abs = whichPath?.trim().split(/\r?\n/).find(Boolean);
1568
+ if (!abs) return trimmed;
1569
+ return `${abs}${rest}`;
1570
+ }
1571
+ function withExportedPath(command, pathValue) {
1572
+ const trimmed = command.trim();
1573
+ if (!trimmed) return trimmed;
1574
+ if (/^(export\s+PATH=|PATH=)/.test(trimmed)) return trimmed;
1575
+ return `export PATH=${posixShellSingleQuote(pathValue)}; ${trimmed}`;
1576
+ }
1577
+ var import_node_fs4, import_node_child_process, import_node_os3, import_node_path4, EXTRA_BIN_DIRS;
1526
1578
  var init_path = __esm({
1527
1579
  "src/agents/path.ts"() {
1528
1580
  "use strict";
1581
+ import_node_fs4 = require("fs");
1582
+ import_node_child_process = require("child_process");
1529
1583
  import_node_os3 = require("os");
1530
1584
  import_node_path4 = require("path");
1531
1585
  EXTRA_BIN_DIRS = [
@@ -1533,6 +1587,10 @@ var init_path = __esm({
1533
1587
  ".cargo/bin",
1534
1588
  ".nvm/current/bin",
1535
1589
  ".asdf/shims",
1590
+ ".volta/bin",
1591
+ ".npm-global/bin",
1592
+ // fnm default alias (common when shell init is skipped)
1593
+ ".local/share/fnm/aliases/default/bin",
1536
1594
  // pnpm / npm global bins (where `@brightsy/cli` typically lands)
1537
1595
  "Library/pnpm",
1538
1596
  ".pnpm"
@@ -2592,7 +2650,7 @@ function isLocalPrFetchBranch(ref) {
2592
2650
  async function createThreadWorktree(opts) {
2593
2651
  let branchName = `thread/${opts.slug}`;
2594
2652
  const worktreePath = (0, import_node_path5.join)(worktreesRoot(opts.repoPath), opts.slug);
2595
- if ((0, import_node_fs4.existsSync)(worktreePath)) {
2653
+ if ((0, import_node_fs5.existsSync)(worktreePath)) {
2596
2654
  throw new Error(`Worktree already exists at ${worktreePath}`);
2597
2655
  }
2598
2656
  await ensureGhPreferOrigin(opts.repoPath);
@@ -2662,7 +2720,7 @@ async function createExistingBranchWorktree(opts) {
2662
2720
  const branchName = opts.branchName.trim();
2663
2721
  if (!branchName) throw new Error("branch name required");
2664
2722
  const worktreePath = (0, import_node_path5.join)(worktreesRoot(opts.repoPath), opts.slug);
2665
- if ((0, import_node_fs4.existsSync)(worktreePath)) {
2723
+ if ((0, import_node_fs5.existsSync)(worktreePath)) {
2666
2724
  throw new Error(`Worktree already exists at ${worktreePath}`);
2667
2725
  }
2668
2726
  await ensureGhPreferOrigin(opts.repoPath);
@@ -2923,9 +2981,9 @@ function sameRepoPath(a, b) {
2923
2981
  }
2924
2982
  function listLocalThreadBranchSlugs(repoPath) {
2925
2983
  const refsDir = (0, import_node_path5.join)(repoPath, ".git", "refs", "heads", "thread");
2926
- if (!(0, import_node_fs4.existsSync)(refsDir)) return [];
2984
+ if (!(0, import_node_fs5.existsSync)(refsDir)) return [];
2927
2985
  try {
2928
- return (0, import_node_fs4.readdirSync)(refsDir).filter((name) => !name.startsWith(".")).map((name) => normalizeTakenSlug(name));
2986
+ return (0, import_node_fs5.readdirSync)(refsDir).filter((name) => !name.startsWith(".")).map((name) => normalizeTakenSlug(name));
2929
2987
  } catch {
2930
2988
  return [];
2931
2989
  }
@@ -2933,8 +2991,8 @@ function listLocalThreadBranchSlugs(repoPath) {
2933
2991
  function collectTakenTeamSlugs(repoPath) {
2934
2992
  const taken = /* @__PURE__ */ new Set();
2935
2993
  const root = worktreesRoot(repoPath);
2936
- if ((0, import_node_fs4.existsSync)(root)) {
2937
- for (const entry of (0, import_node_fs4.readdirSync)(root, { withFileTypes: true })) {
2994
+ if ((0, import_node_fs5.existsSync)(root)) {
2995
+ for (const entry of (0, import_node_fs5.readdirSync)(root, { withFileTypes: true })) {
2938
2996
  if (entry.isDirectory() && entry.name !== ".DS_Store") {
2939
2997
  taken.add(normalizeTakenSlug(entry.name));
2940
2998
  }
@@ -2956,16 +3014,16 @@ function allocateTeamSlug(repoPath) {
2956
3014
  for (let attempt = 0; attempt < 32; attempt++) {
2957
3015
  const team = allocateTeamName(taken);
2958
3016
  const path = (0, import_node_path5.join)(worktreesRoot(repoPath), team.slug);
2959
- if (!(0, import_node_fs4.existsSync)(path)) return team;
3017
+ if (!(0, import_node_fs5.existsSync)(path)) return team;
2960
3018
  taken.add(team.slug);
2961
3019
  }
2962
3020
  throw new Error("No available soccer team worktree directories left");
2963
3021
  }
2964
- var import_node_fs4, import_node_path5;
3022
+ var import_node_fs5, import_node_path5;
2965
3023
  var init_worktree = __esm({
2966
3024
  "src/git/worktree.ts"() {
2967
3025
  "use strict";
2968
- import_node_fs4 = require("fs");
3026
+ import_node_fs5 = require("fs");
2969
3027
  import_node_path5 = require("path");
2970
3028
  init_paths();
2971
3029
  init_thread_store();
@@ -2987,11 +3045,13 @@ __export(app_settings_exports, {
2987
3045
  HARNESS_ENV_KEYS: () => HARNESS_ENV_KEYS,
2988
3046
  appSettingsPath: () => appSettingsPath,
2989
3047
  applyAppEnvironment: () => applyAppEnvironment,
3048
+ autoArchiveOnMergeEnabled: () => autoArchiveOnMergeEnabled,
2990
3049
  autoCleanupOrphansEnabled: () => autoCleanupOrphansEnabled,
2991
3050
  autoRenameBranchEnabled: () => autoRenameBranchEnabled,
2992
3051
  autoRunAfterSetupEnabled: () => autoRunAfterSetupEnabled,
2993
3052
  brightsyCloudConnectAgent: () => brightsyCloudConnectAgent,
2994
3053
  brightsyCloudConnectEnabled: () => brightsyCloudConnectEnabled,
3054
+ brightsyInjectWorktreeMcpEnabled: () => brightsyInjectWorktreeMcpEnabled,
2995
3055
  caffeinateWhileCloudConnectEnabled: () => caffeinateWhileCloudConnectEnabled,
2996
3056
  caffeinateWhileRunningEnabled: () => caffeinateWhileRunningEnabled,
2997
3057
  childEnvWithAppSettings: () => childEnvWithAppSettings,
@@ -3010,17 +3070,21 @@ __export(app_settings_exports, {
3010
3070
  maxConcurrentAgents: () => maxConcurrentAgents,
3011
3071
  orchestrationQuotaFallbackAgent: () => orchestrationQuotaFallbackAgent,
3012
3072
  orchestrationQuotaOnLimit: () => orchestrationQuotaOnLimit,
3073
+ resolveAgentExecutable: () => resolveAgentExecutable,
3013
3074
  resolveClaudeExecutable: () => resolveClaudeExecutable,
3014
3075
  resolveEffectiveIssueSource: () => resolveEffectiveIssueSource,
3015
3076
  resolveNewThreadOptions: () => resolveNewThreadOptions,
3016
3077
  resolveThreadDefaults: () => resolveThreadDefaults,
3017
3078
  saveAppSettings: () => saveAppSettings,
3018
3079
  updateAdvancedSettings: () => updateAdvancedSettings,
3080
+ updateAgentExecutable: () => updateAgentExecutable,
3019
3081
  updateAppEnvironment: () => updateAppEnvironment,
3020
3082
  updateBrightsySettings: () => updateBrightsySettings,
3021
3083
  updateClaudeSettings: () => updateClaudeSettings,
3084
+ updateCodexSettings: () => updateCodexSettings,
3022
3085
  updateDefaultsSettings: () => updateDefaultsSettings,
3023
- updateIntegrationsSettings: () => updateIntegrationsSettings
3086
+ updateIntegrationsSettings: () => updateIntegrationsSettings,
3087
+ updateOpencodeSettings: () => updateOpencodeSettings
3024
3088
  });
3025
3089
  function appSettingsPath() {
3026
3090
  return (0, import_node_path6.join)(appDataDir(), "settings.json");
@@ -3041,16 +3105,33 @@ function normalizeClaude(raw) {
3041
3105
  }
3042
3106
  return out;
3043
3107
  }
3108
+ function normalizeCliExecutable(raw) {
3109
+ if (!raw || typeof raw !== "object") return {};
3110
+ const source = raw;
3111
+ const out = {};
3112
+ if (typeof source.executablePath === "string") {
3113
+ const path = source.executablePath.trim();
3114
+ if (path) out.executablePath = path;
3115
+ }
3116
+ return out;
3117
+ }
3044
3118
  function normalizeBrightsy(raw) {
3045
3119
  if (!raw || typeof raw !== "object") return {};
3046
3120
  const source = raw;
3047
3121
  const out = {};
3122
+ if (typeof source.executablePath === "string") {
3123
+ const path = source.executablePath.trim();
3124
+ if (path) out.executablePath = path;
3125
+ }
3048
3126
  if (typeof source.cloudConnectEnabled === "boolean") {
3049
3127
  out.cloudConnectEnabled = source.cloudConnectEnabled;
3050
3128
  }
3051
3129
  if (typeof source.cloudConnectAgent === "string" && CLOUD_CONNECT_AGENTS.has(source.cloudConnectAgent)) {
3052
3130
  out.cloudConnectAgent = source.cloudConnectAgent;
3053
3131
  }
3132
+ if (typeof source.injectWorktreeMcp === "boolean") {
3133
+ out.injectWorktreeMcp = source.injectWorktreeMcp;
3134
+ }
3054
3135
  return out;
3055
3136
  }
3056
3137
  function normalizeIntegrations(raw) {
@@ -3104,6 +3185,9 @@ function normalizeAdvanced(raw) {
3104
3185
  if (typeof source.deleteBranchOnPurge === "boolean") {
3105
3186
  out.deleteBranchOnPurge = source.deleteBranchOnPurge;
3106
3187
  }
3188
+ if (typeof source.autoArchiveOnMerge === "boolean") {
3189
+ out.autoArchiveOnMerge = source.autoArchiveOnMerge;
3190
+ }
3107
3191
  if (typeof source.maxConcurrent === "number" && Number.isFinite(source.maxConcurrent)) {
3108
3192
  out.maxConcurrent = Math.max(1, Math.min(32, Math.floor(source.maxConcurrent)));
3109
3193
  }
@@ -3133,6 +3217,8 @@ function normalizeAdvanced(raw) {
3133
3217
  function normalizeSettings(raw) {
3134
3218
  const env = {};
3135
3219
  let claude = {};
3220
+ let codex = {};
3221
+ let opencode = {};
3136
3222
  let brightsy = {};
3137
3223
  let integrations = {};
3138
3224
  let defaults = {};
@@ -3151,6 +3237,12 @@ function normalizeSettings(raw) {
3151
3237
  if ("claude" in raw) {
3152
3238
  claude = normalizeClaude(raw.claude);
3153
3239
  }
3240
+ if ("codex" in raw) {
3241
+ codex = normalizeCliExecutable(raw.codex);
3242
+ }
3243
+ if ("opencode" in raw) {
3244
+ opencode = normalizeCliExecutable(raw.opencode);
3245
+ }
3154
3246
  if ("brightsy" in raw) {
3155
3247
  brightsy = normalizeBrightsy(raw.brightsy);
3156
3248
  }
@@ -3166,15 +3258,24 @@ function normalizeSettings(raw) {
3166
3258
  advanced = normalizeAdvanced(raw.advanced);
3167
3259
  }
3168
3260
  }
3169
- return { environment: env, claude, brightsy, integrations, defaults, advanced };
3261
+ return {
3262
+ environment: env,
3263
+ claude,
3264
+ codex,
3265
+ opencode,
3266
+ brightsy,
3267
+ integrations,
3268
+ defaults,
3269
+ advanced
3270
+ };
3170
3271
  }
3171
3272
  function loadAppSettings() {
3172
3273
  const path = appSettingsPath();
3173
- if (!(0, import_node_fs5.existsSync)(path)) {
3274
+ if (!(0, import_node_fs6.existsSync)(path)) {
3174
3275
  return { ...EMPTY_SETTINGS };
3175
3276
  }
3176
3277
  try {
3177
- const parsed = JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
3278
+ const parsed = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
3178
3279
  return normalizeSettings(parsed);
3179
3280
  } catch {
3180
3281
  return { ...EMPTY_SETTINGS };
@@ -3182,8 +3283,8 @@ function loadAppSettings() {
3182
3283
  }
3183
3284
  function saveAppSettings(settings) {
3184
3285
  const normalized = normalizeSettings(settings);
3185
- (0, import_node_fs5.mkdirSync)(appDataDir(), { recursive: true });
3186
- (0, import_node_fs5.writeFileSync)(appSettingsPath(), `${JSON.stringify(normalized, null, 2)}
3286
+ (0, import_node_fs6.mkdirSync)(appDataDir(), { recursive: true });
3287
+ (0, import_node_fs6.writeFileSync)(appSettingsPath(), `${JSON.stringify(normalized, null, 2)}
3187
3288
  `, "utf8");
3188
3289
  return normalized;
3189
3290
  }
@@ -3219,6 +3320,13 @@ function updateClaudeSettings(patch) {
3219
3320
  function updateBrightsySettings(patch) {
3220
3321
  const current = loadAppSettings();
3221
3322
  const brightsy = { ...current.brightsy };
3323
+ if ("executablePath" in patch) {
3324
+ if (patch.executablePath == null || patch.executablePath.trim() === "") {
3325
+ delete brightsy.executablePath;
3326
+ } else {
3327
+ brightsy.executablePath = patch.executablePath.trim();
3328
+ }
3329
+ }
3222
3330
  if (typeof patch.cloudConnectEnabled === "boolean") {
3223
3331
  brightsy.cloudConnectEnabled = patch.cloudConnectEnabled;
3224
3332
  }
@@ -3229,8 +3337,35 @@ function updateBrightsySettings(patch) {
3229
3337
  brightsy.cloudConnectAgent = patch.cloudConnectAgent;
3230
3338
  }
3231
3339
  }
3340
+ if (typeof patch.injectWorktreeMcp === "boolean") {
3341
+ brightsy.injectWorktreeMcp = patch.injectWorktreeMcp;
3342
+ }
3232
3343
  return saveAppSettings({ ...current, brightsy });
3233
3344
  }
3345
+ function updateCodexSettings(patch) {
3346
+ const current = loadAppSettings();
3347
+ const codex = { ...current.codex };
3348
+ if ("executablePath" in patch) {
3349
+ if (patch.executablePath == null || patch.executablePath.trim() === "") {
3350
+ delete codex.executablePath;
3351
+ } else {
3352
+ codex.executablePath = patch.executablePath.trim();
3353
+ }
3354
+ }
3355
+ return saveAppSettings({ ...current, codex });
3356
+ }
3357
+ function updateOpencodeSettings(patch) {
3358
+ const current = loadAppSettings();
3359
+ const opencode = { ...current.opencode };
3360
+ if ("executablePath" in patch) {
3361
+ if (patch.executablePath == null || patch.executablePath.trim() === "") {
3362
+ delete opencode.executablePath;
3363
+ } else {
3364
+ opencode.executablePath = patch.executablePath.trim();
3365
+ }
3366
+ }
3367
+ return saveAppSettings({ ...current, opencode });
3368
+ }
3234
3369
  function updateIntegrationsSettings(patch) {
3235
3370
  const current = loadAppSettings();
3236
3371
  const integrations = { ...current.integrations };
@@ -3338,6 +3473,9 @@ function getLinearApiKey(settings = loadAppSettings()) {
3338
3473
  function brightsyCloudConnectEnabled(settings = loadAppSettings()) {
3339
3474
  return Boolean(settings.brightsy.cloudConnectEnabled);
3340
3475
  }
3476
+ function brightsyInjectWorktreeMcpEnabled(settings = loadAppSettings()) {
3477
+ return Boolean(settings.brightsy.injectWorktreeMcp);
3478
+ }
3341
3479
  function brightsyCloudConnectAgent(settings = loadAppSettings()) {
3342
3480
  const fallback = settings.brightsy.cloudConnectAgent && CLOUD_CONNECT_AGENTS.has(settings.brightsy.cloudConnectAgent) ? settings.brightsy.cloudConnectAgent : "claude";
3343
3481
  const preferred = getDefaultAgent(settings);
@@ -3364,6 +3502,9 @@ function updateAdvancedSettings(patch) {
3364
3502
  if (typeof patch.deleteBranchOnPurge === "boolean") {
3365
3503
  advanced.deleteBranchOnPurge = patch.deleteBranchOnPurge;
3366
3504
  }
3505
+ if (typeof patch.autoArchiveOnMerge === "boolean") {
3506
+ advanced.autoArchiveOnMerge = patch.autoArchiveOnMerge;
3507
+ }
3367
3508
  if (typeof patch.maxConcurrent === "number" && Number.isFinite(patch.maxConcurrent)) {
3368
3509
  advanced.maxConcurrent = Math.max(1, Math.min(32, Math.floor(patch.maxConcurrent)));
3369
3510
  }
@@ -3405,6 +3546,9 @@ function caffeinateWhileCloudConnectEnabled(settings = loadAppSettings()) {
3405
3546
  function deleteBranchOnPurgeEnabled(settings = loadAppSettings()) {
3406
3547
  return Boolean(settings.advanced.deleteBranchOnPurge);
3407
3548
  }
3549
+ function autoArchiveOnMergeEnabled(settings = loadAppSettings()) {
3550
+ return Boolean(settings.advanced.autoArchiveOnMerge);
3551
+ }
3408
3552
  function autoCleanupOrphansEnabled(settings = loadAppSettings()) {
3409
3553
  return Boolean(settings.advanced.autoCleanupOrphans);
3410
3554
  }
@@ -3426,8 +3570,36 @@ function maxConcurrentAgents(settings = loadAppSettings()) {
3426
3570
  return 3;
3427
3571
  }
3428
3572
  function resolveClaudeExecutable(settings = loadAppSettings()) {
3429
- const override = settings.claude.executablePath?.trim();
3430
- return override || "claude";
3573
+ return resolveAgentExecutable("claude", settings);
3574
+ }
3575
+ function resolveAgentExecutable(agent, settings = loadAppSettings()) {
3576
+ const fallback = DEFAULT_CLI_BIN[agent];
3577
+ if (agent === "claude") {
3578
+ const override2 = settings.claude.executablePath?.trim();
3579
+ return override2 || fallback;
3580
+ }
3581
+ if (agent === "codex") {
3582
+ const override2 = settings.codex.executablePath?.trim();
3583
+ return override2 || fallback;
3584
+ }
3585
+ if (agent === "opencode") {
3586
+ const override2 = settings.opencode.executablePath?.trim();
3587
+ return override2 || fallback;
3588
+ }
3589
+ const override = settings.brightsy.executablePath?.trim();
3590
+ return override || fallback;
3591
+ }
3592
+ function updateAgentExecutable(agent, executablePath) {
3593
+ if (agent === "claude") {
3594
+ return updateClaudeSettings({ executablePath });
3595
+ }
3596
+ if (agent === "codex") {
3597
+ return updateCodexSettings({ executablePath });
3598
+ }
3599
+ if (agent === "opencode") {
3600
+ return updateOpencodeSettings({ executablePath });
3601
+ }
3602
+ return updateBrightsySettings({ executablePath });
3431
3603
  }
3432
3604
  function claudeChromeEnabled(settings = loadAppSettings()) {
3433
3605
  return Boolean(settings.claude.chromeEnabled);
@@ -3455,11 +3627,11 @@ function childEnvWithAppSettings(extra) {
3455
3627
  function harnessEnvKey(harness) {
3456
3628
  return HARNESS_ENV_KEYS[harness];
3457
3629
  }
3458
- var import_node_fs5, import_node_os4, import_node_path6, HARNESS_ENV_KEYS, DEFAULT_AGENTS, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, EMPTY_SETTINGS;
3630
+ var import_node_fs6, import_node_os4, import_node_path6, HARNESS_ENV_KEYS, DEFAULT_AGENTS, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, EMPTY_SETTINGS, DEFAULT_CLI_BIN;
3459
3631
  var init_app_settings = __esm({
3460
3632
  "src/store/app-settings.ts"() {
3461
3633
  "use strict";
3462
- import_node_fs5 = require("fs");
3634
+ import_node_fs6 = require("fs");
3463
3635
  import_node_os4 = require("os");
3464
3636
  import_node_path6 = require("path");
3465
3637
  init_thinking_effort();
@@ -3488,11 +3660,19 @@ var init_app_settings = __esm({
3488
3660
  EMPTY_SETTINGS = {
3489
3661
  environment: {},
3490
3662
  claude: {},
3663
+ codex: {},
3664
+ opencode: {},
3491
3665
  brightsy: {},
3492
3666
  integrations: {},
3493
3667
  defaults: {},
3494
3668
  advanced: {}
3495
3669
  };
3670
+ DEFAULT_CLI_BIN = {
3671
+ claude: "claude",
3672
+ codex: "codex",
3673
+ opencode: "opencode",
3674
+ brightsy: "brightsy"
3675
+ };
3496
3676
  }
3497
3677
  });
3498
3678
 
@@ -3618,7 +3798,7 @@ function coordinatorTurnReminder(opts) {
3618
3798
  function ensureGlobalCoordinatorCwd(opts) {
3619
3799
  const dir = globalAgentCwd();
3620
3800
  try {
3621
- (0, import_node_fs6.mkdirSync)(dir, { recursive: true });
3801
+ (0, import_node_fs7.mkdirSync)(dir, { recursive: true });
3622
3802
  } catch {
3623
3803
  return dir;
3624
3804
  }
@@ -3626,7 +3806,7 @@ function ensureGlobalCoordinatorCwd(opts) {
3626
3806
  let orchId = opts?.orchestratorThreadId?.trim() || "";
3627
3807
  if (!orchId) {
3628
3808
  try {
3629
- const existing = (0, import_node_fs6.readFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), "utf8");
3809
+ const existing = (0, import_node_fs7.readFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), "utf8");
3630
3810
  const m = existing.match(
3631
3811
  /YOUR orchestration thread id is `([0-9a-f-]{36})`/i
3632
3812
  );
@@ -3668,9 +3848,9 @@ function ensureGlobalCoordinatorCwd(opts) {
3668
3848
  "Always ask worktree agents to open draft PRs (`send_to_thread` + `gh pr create --draft -R <origin>`); never open PRs from the orchestrator."
3669
3849
  ].join("\n");
3670
3850
  try {
3671
- (0, import_node_fs6.writeFileSync)((0, import_node_path7.join)(dir, "CLAUDE.md"), `${body}
3851
+ (0, import_node_fs7.writeFileSync)((0, import_node_path7.join)(dir, "CLAUDE.md"), `${body}
3672
3852
  `, "utf8");
3673
- (0, import_node_fs6.writeFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), `${body}
3853
+ (0, import_node_fs7.writeFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), `${body}
3674
3854
  `, "utf8");
3675
3855
  } catch {
3676
3856
  }
@@ -3703,11 +3883,11 @@ function coordinatorSystemPrompt(opts) {
3703
3883
  formatWorkspaceInventory(opts.workspaces)
3704
3884
  ].join("\n");
3705
3885
  }
3706
- var import_node_fs6, import_node_path7, COORDINATOR_TOOL_PLAYBOOK;
3886
+ var import_node_fs7, import_node_path7, COORDINATOR_TOOL_PLAYBOOK;
3707
3887
  var init_coordinator_prompt = __esm({
3708
3888
  "src/orchestrator/coordinator-prompt.ts"() {
3709
3889
  "use strict";
3710
- import_node_fs6 = require("fs");
3890
+ import_node_fs7 = require("fs");
3711
3891
  import_node_path7 = require("path");
3712
3892
  init_worktree();
3713
3893
  init_app_settings();
@@ -3933,26 +4113,26 @@ function brightsyConfigPath() {
3933
4113
  }
3934
4114
  function loadBrightsyConfig() {
3935
4115
  const path = brightsyConfigPath();
3936
- if (!(0, import_node_fs7.existsSync)(path)) {
4116
+ if (!(0, import_node_fs8.existsSync)(path)) {
3937
4117
  throw new Error("Brightsy not logged in \u2014 run `brightsy login` first");
3938
4118
  }
3939
- const raw = JSON.parse((0, import_node_fs7.readFileSync)(path, "utf8"));
4119
+ const raw = JSON.parse((0, import_node_fs8.readFileSync)(path, "utf8"));
3940
4120
  if (!raw.access_token || !raw.account_id) {
3941
4121
  throw new Error("Brightsy config incomplete \u2014 run `brightsy login`");
3942
4122
  }
3943
4123
  return raw;
3944
4124
  }
3945
4125
  function saveBrightsyConfig(cfg) {
3946
- (0, import_node_fs7.writeFileSync)(brightsyConfigPath(), `${JSON.stringify(cfg, null, 2)}
4126
+ (0, import_node_fs8.writeFileSync)(brightsyConfigPath(), `${JSON.stringify(cfg, null, 2)}
3947
4127
  `, {
3948
4128
  mode: 384
3949
4129
  });
3950
4130
  }
3951
- var import_node_fs7, import_node_os5, import_node_path8;
4131
+ var import_node_fs8, import_node_os5, import_node_path8;
3952
4132
  var init_config = __esm({
3953
4133
  "src/brightsy/config.ts"() {
3954
4134
  "use strict";
3955
- import_node_fs7 = require("fs");
4135
+ import_node_fs8 = require("fs");
3956
4136
  import_node_os5 = require("os");
3957
4137
  import_node_path8 = require("path");
3958
4138
  }
@@ -3973,18 +4153,18 @@ function storePath() {
3973
4153
  }
3974
4154
  function readStore() {
3975
4155
  const path = storePath();
3976
- if (!(0, import_node_fs8.existsSync)(path)) return [];
4156
+ if (!(0, import_node_fs9.existsSync)(path)) return [];
3977
4157
  try {
3978
- const parsed = JSON.parse((0, import_node_fs8.readFileSync)(path, "utf8"));
4158
+ const parsed = JSON.parse((0, import_node_fs9.readFileSync)(path, "utf8"));
3979
4159
  return Array.isArray(parsed.teams) ? parsed.teams : [];
3980
4160
  } catch {
3981
4161
  return [];
3982
4162
  }
3983
4163
  }
3984
4164
  function writeStore(teams) {
3985
- (0, import_node_fs8.mkdirSync)(appDataDir(), { recursive: true });
4165
+ (0, import_node_fs9.mkdirSync)(appDataDir(), { recursive: true });
3986
4166
  const path = storePath();
3987
- (0, import_node_fs8.writeFileSync)(path, `${JSON.stringify({ teams }, null, 2)}
4167
+ (0, import_node_fs9.writeFileSync)(path, `${JSON.stringify({ teams }, null, 2)}
3988
4168
  `, {
3989
4169
  mode: 384
3990
4170
  });
@@ -4096,11 +4276,11 @@ function brightsyMcpServerName(slug) {
4096
4276
  const cleaned = slug.replace(/[^A-Za-z0-9_-]/g, "_").replace(/^_+|_+$/g, "");
4097
4277
  return `brightsy_${cleaned || "team"}`;
4098
4278
  }
4099
- var import_node_fs8, import_node_path9;
4279
+ var import_node_fs9, import_node_path9;
4100
4280
  var init_connected_teams = __esm({
4101
4281
  "src/brightsy/connected-teams.ts"() {
4102
4282
  "use strict";
4103
- import_node_fs8 = require("fs");
4283
+ import_node_fs9 = require("fs");
4104
4284
  import_node_path9 = require("path");
4105
4285
  init_paths();
4106
4286
  init_accounts();
@@ -4148,6 +4328,11 @@ function normalizeTurnInput(input) {
4148
4328
  if (typeof input === "string") return { prompt: input };
4149
4329
  return { prompt: input.prompt, cachedPrefix: input.cachedPrefix?.trim() || void 0 };
4150
4330
  }
4331
+ function dropCachedPrefixOnResume(input, sessionId) {
4332
+ const turn = normalizeTurnInput(input);
4333
+ if (sessionId) return { prompt: turn.prompt };
4334
+ return turn;
4335
+ }
4151
4336
  function flattenTurnInput(input) {
4152
4337
  const { cachedPrefix, prompt } = normalizeTurnInput(input);
4153
4338
  if (!cachedPrefix) return prompt;
@@ -4292,9 +4477,13 @@ async function fetchTeamChatTargets(team) {
4292
4477
  };
4293
4478
  }
4294
4479
  async function listBrightsyChatTargetsViaCli() {
4295
- const listed = await run("brightsy", ["chat", "--list-targets", "--json"], {
4296
- reject: false
4297
- });
4480
+ const listed = await run(
4481
+ resolveAgentExecutable("brightsy"),
4482
+ ["chat", "--list-targets", "--json"],
4483
+ {
4484
+ reject: false
4485
+ }
4486
+ );
4298
4487
  if (listed.exitCode !== 0 || !listed.stdout.trim()) {
4299
4488
  throw new Error(
4300
4489
  listed.stderr.trim() || "Failed to list Brightsy chat targets \u2014 is `brightsy` installed and logged in?"
@@ -4378,13 +4567,15 @@ async function syncCliForTarget(accountId) {
4378
4567
  }
4379
4568
  applyConnectedTeamToCli(team);
4380
4569
  }
4381
- var brightsyAdapter;
4570
+ var import_node_fs10, brightsyAdapter;
4382
4571
  var init_brightsy = __esm({
4383
4572
  "src/agents/brightsy.ts"() {
4384
4573
  "use strict";
4574
+ import_node_fs10 = require("fs");
4385
4575
  init_run();
4386
4576
  init_connected_teams();
4387
4577
  init_config();
4578
+ init_app_settings();
4388
4579
  init_brightsy_targets();
4389
4580
  init_error_detail();
4390
4581
  init_turn_input();
@@ -4392,18 +4583,32 @@ var init_brightsy = __esm({
4392
4583
  brightsyAdapter = {
4393
4584
  kind: "brightsy",
4394
4585
  async detect() {
4395
- const which = await run("which", ["brightsy"], { reject: false });
4396
- if (which.exitCode !== 0) {
4397
- return {
4398
- agent: "brightsy",
4399
- installed: false,
4400
- authenticated: false,
4401
- linearMcp: false,
4402
- warnings: [],
4403
- reason: "brightsy CLI not found on PATH \u2014 npm i -g @brightsy/cli"
4404
- };
4586
+ const brightsy = resolveAgentExecutable("brightsy");
4587
+ if (brightsy !== "brightsy") {
4588
+ if (!(0, import_node_fs10.existsSync)(brightsy)) {
4589
+ return {
4590
+ agent: "brightsy",
4591
+ installed: false,
4592
+ authenticated: false,
4593
+ linearMcp: false,
4594
+ warnings: [],
4595
+ reason: `Brightsy executable not found: ${brightsy}`
4596
+ };
4597
+ }
4598
+ } else {
4599
+ const which = await run("which", ["brightsy"], { reject: false });
4600
+ if (which.exitCode !== 0) {
4601
+ return {
4602
+ agent: "brightsy",
4603
+ installed: false,
4604
+ authenticated: false,
4605
+ linearMcp: false,
4606
+ warnings: [],
4607
+ reason: "brightsy CLI not found on PATH \u2014 npm i -g @brightsy/cli"
4608
+ };
4609
+ }
4405
4610
  }
4406
- const who = await run("brightsy", ["whoami"], { reject: false });
4611
+ const who = await run(brightsy, ["whoami"], { reject: false });
4407
4612
  const authenticated = who.exitCode === 0 && /logged in as/i.test(who.stdout);
4408
4613
  return {
4409
4614
  agent: "brightsy",
@@ -4428,7 +4633,7 @@ var init_brightsy = __esm({
4428
4633
  target.id
4429
4634
  ];
4430
4635
  return {
4431
- file: "brightsy",
4636
+ file: resolveAgentExecutable("brightsy"),
4432
4637
  args,
4433
4638
  cwd: thread.worktreePath,
4434
4639
  // Piped stdin becomes the message body (avoids ARG_MAX for long seeds).
@@ -4445,7 +4650,11 @@ var init_brightsy = __esm({
4445
4650
  const target = decodeBrightsyTarget(thread.model);
4446
4651
  await syncCliForTarget(target.accountId);
4447
4652
  const args = target.type === "model" ? ["chat", "--model", target.id] : ["chat", "--agent", target.id];
4448
- return { file: "brightsy", args, cwd: thread.worktreePath };
4653
+ return {
4654
+ file: resolveAgentExecutable("brightsy"),
4655
+ args,
4656
+ cwd: thread.worktreePath
4657
+ };
4449
4658
  }
4450
4659
  };
4451
4660
  }
@@ -4490,6 +4699,18 @@ var init_claude_mcp = __esm({
4490
4699
  }
4491
4700
  });
4492
4701
 
4702
+ // src/mcp/profile.ts
4703
+ function sideboardMcpProfile(env = process.env) {
4704
+ return env[SIDEBOARD_MCP_PROFILE_ENV]?.trim().toLowerCase() === "worktree" ? "worktree" : "orchestration";
4705
+ }
4706
+ var SIDEBOARD_MCP_PROFILE_ENV;
4707
+ var init_profile = __esm({
4708
+ "src/mcp/profile.ts"() {
4709
+ "use strict";
4710
+ SIDEBOARD_MCP_PROFILE_ENV = "SIDEBOARD_MCP_PROFILE";
4711
+ }
4712
+ });
4713
+
4493
4714
  // src/agents/node-launch.ts
4494
4715
  function isAsarPath(filePath) {
4495
4716
  return /\.asar([/\\]|$)/.test(filePath);
@@ -4537,6 +4758,38 @@ function isBrightsyConnected() {
4537
4758
  return false;
4538
4759
  }
4539
4760
  }
4761
+ function promptMentionsBrightsy(text) {
4762
+ return BRIGHTSY_WORD.test(text ?? "");
4763
+ }
4764
+ function isBrightsyMcpToolName(name) {
4765
+ const n = name.toLowerCase();
4766
+ return n === "brightsy" || n.startsWith("mcp__brightsy") || n.startsWith("brightsy_");
4767
+ }
4768
+ function toolInputUsesBrightsyDatasource(input) {
4769
+ const ds = input?.datasource;
4770
+ return typeof ds === "string" && ds.toLowerCase() === "brightsy";
4771
+ }
4772
+ function messageUsedBrightsyMcp(msg) {
4773
+ if (msg.role === "user" && promptMentionsBrightsy(msg.text)) return true;
4774
+ for (const part of msg.parts ?? []) {
4775
+ if (part.type !== "tool") continue;
4776
+ if (isBrightsyMcpToolName(part.name)) return true;
4777
+ if (/present_(schema|files)$/i.test(part.name) && toolInputUsesBrightsyDatasource(part.input)) {
4778
+ return true;
4779
+ }
4780
+ }
4781
+ return false;
4782
+ }
4783
+ function threadRequestsBrightsyMcp(thread) {
4784
+ return (thread?.messages ?? []).some(messageUsedBrightsyMcp);
4785
+ }
4786
+ function shouldInjectBrightsyMcp(thread, opts) {
4787
+ if (!(opts?.connected ?? isBrightsyConnected())) return false;
4788
+ if (opts?.orchestrator) return true;
4789
+ const alwaysOn = opts?.alwaysOnWorktree ?? Boolean(loadAppSettings().brightsy?.injectWorktreeMcp);
4790
+ if (alwaysOn) return true;
4791
+ return threadRequestsBrightsyMcp(thread);
4792
+ }
4540
4793
  function mcpLaunch(cmd, name, env) {
4541
4794
  if (cmd === "brightsy-mcp") {
4542
4795
  return { name, command: "brightsy-mcp", ...env ? { env } : {} };
@@ -4582,7 +4835,7 @@ function corePackageDir() {
4582
4835
  }
4583
4836
  function findSideboardMcpJsEntry() {
4584
4837
  const override = process.env.SIDEBOARD_MCP_ENTRY?.trim() || process.env.SIDEBOARD_CLI?.trim();
4585
- if (override && (0, import_node_fs9.existsSync)(override)) return override;
4838
+ if (override && (0, import_node_fs11.existsSync)(override)) return override;
4586
4839
  let dir = corePackageDir();
4587
4840
  for (let i = 0; i < 10; i++) {
4588
4841
  const candidates = [
@@ -4595,7 +4848,7 @@ function findSideboardMcpJsEntry() {
4595
4848
  (0, import_node_path10.join)(dir, "cli/dist/index.js")
4596
4849
  ];
4597
4850
  for (const p of candidates) {
4598
- if ((0, import_node_fs9.existsSync)(p)) return p;
4851
+ if ((0, import_node_fs11.existsSync)(p)) return p;
4599
4852
  }
4600
4853
  const parent = (0, import_node_path10.dirname)(dir);
4601
4854
  if (parent === dir) break;
@@ -4626,11 +4879,13 @@ async function buildInjectedMcpServers(opts) {
4626
4879
  const servers = [];
4627
4880
  if (opts.includeSideboard) {
4628
4881
  const sideboard = await resolveSideboardMcpServer();
4882
+ const orchId = opts.orchestratorThreadId?.trim();
4883
+ const profile = orchId ? "orchestration" : "worktree";
4629
4884
  sideboard.env = {
4630
4885
  ...sideboard.env ?? {},
4631
- SIDEBOARD_APP_DATA: appDataDir()
4886
+ SIDEBOARD_APP_DATA: appDataDir(),
4887
+ [SIDEBOARD_MCP_PROFILE_ENV]: profile
4632
4888
  };
4633
- const orchId = opts.orchestratorThreadId?.trim();
4634
4889
  if (orchId) {
4635
4890
  sideboard.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID = orchId;
4636
4891
  }
@@ -4705,16 +4960,16 @@ function writeMcpServersConfig(servers) {
4705
4960
  ...s.env ? { env: s.env } : {}
4706
4961
  };
4707
4962
  }
4708
- const dir = (0, import_node_fs9.mkdtempSync)((0, import_node_path10.join)((0, import_node_os6.tmpdir)(), "sideboard-mcp-"));
4963
+ const dir = (0, import_node_fs11.mkdtempSync)((0, import_node_path10.join)((0, import_node_os6.tmpdir)(), "sideboard-mcp-"));
4709
4964
  const cfgPath = (0, import_node_path10.join)(dir, "mcp.json");
4710
- (0, import_node_fs9.writeFileSync)(cfgPath, JSON.stringify({ mcpServers }, null, 2));
4965
+ (0, import_node_fs11.writeFileSync)(cfgPath, JSON.stringify({ mcpServers }, null, 2));
4711
4966
  return cfgPath;
4712
4967
  }
4713
- var import_node_fs9, import_node_module, import_node_os6, import_node_path10, import_node_url, import_meta, SIDEBOARD_MCP_ALLOWED_TOOLS, SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS, brightsyMcpCommandCache;
4968
+ var import_node_fs11, import_node_module, import_node_os6, import_node_path10, import_node_url, import_meta, SIDEBOARD_MCP_ALLOWED_TOOLS, SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS, brightsyMcpCommandCache, BRIGHTSY_WORD;
4714
4969
  var init_injected_mcp = __esm({
4715
4970
  "src/agents/injected-mcp.ts"() {
4716
4971
  "use strict";
4717
- import_node_fs9 = require("fs");
4972
+ import_node_fs11 = require("fs");
4718
4973
  import_node_module = require("module");
4719
4974
  import_node_os6 = require("os");
4720
4975
  import_node_path10 = require("path");
@@ -4722,6 +4977,8 @@ var init_injected_mcp = __esm({
4722
4977
  init_run();
4723
4978
  init_config();
4724
4979
  init_connected_teams();
4980
+ init_profile();
4981
+ init_app_settings();
4725
4982
  init_paths();
4726
4983
  init_node_launch();
4727
4984
  import_meta = {};
@@ -4737,6 +4994,7 @@ var init_injected_mcp = __esm({
4737
4994
  "mcp__sideboard__present_plan"
4738
4995
  ];
4739
4996
  brightsyMcpCommandCache = null;
4997
+ BRIGHTSY_WORD = /(?<![a-z0-9_-])brightsy(?![a-z0-9_-])/i;
4740
4998
  }
4741
4999
  });
4742
5000
 
@@ -4891,11 +5149,11 @@ function parseIssuesJson(raw) {
4891
5149
  }
4892
5150
  return [];
4893
5151
  }
4894
- var import_node_fs10, BASE_ALLOWED_TOOLS, CLAUDE_CHROME_ALLOWED_TOOLS, CLAUDE_PROMPT_ARG_MAX, claudeAdapter;
5152
+ var import_node_fs12, BASE_ALLOWED_TOOLS, CLAUDE_CHROME_ALLOWED_TOOLS, CLAUDE_PROMPT_ARG_MAX, claudeAdapter;
4895
5153
  var init_claude = __esm({
4896
5154
  "src/agents/claude.ts"() {
4897
5155
  "use strict";
4898
- import_node_fs10 = require("fs");
5156
+ import_node_fs12 = require("fs");
4899
5157
  init_run();
4900
5158
  init_app_settings();
4901
5159
  init_claude_mcp();
@@ -4924,7 +5182,7 @@ var init_claude = __esm({
4924
5182
  async detect() {
4925
5183
  const claude = resolveClaudeExecutable();
4926
5184
  if (claude !== "claude") {
4927
- if (!(0, import_node_fs10.existsSync)(claude)) {
5185
+ if (!(0, import_node_fs12.existsSync)(claude)) {
4928
5186
  return {
4929
5187
  agent: "claude",
4930
5188
  installed: false,
@@ -4963,9 +5221,8 @@ var init_claude = __esm({
4963
5221
  },
4964
5222
  async buildTurn(thread, input) {
4965
5223
  const claude = resolveClaudeExecutable();
4966
- const turn = normalizeTurnInput(input);
4967
5224
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
4968
- const effective = sessionId ? { prompt: turn.prompt } : turn;
5225
+ const effective = dropCachedPrefixOnResume(input, sessionId);
4969
5226
  const promptText = flattenTurnInput(effective);
4970
5227
  const useStdin = promptText.length > CLAUDE_PROMPT_ARG_MAX;
4971
5228
  if (process.env.SIDEBOARD_DEBUG_CLAUDE_TURN === "1") {
@@ -4978,7 +5235,9 @@ var init_claude = __esm({
4978
5235
  const isOrchestrator = isOrchestratorThread2(thread);
4979
5236
  const injectedServers = await buildInjectedMcpServers({
4980
5237
  includeSideboard: true,
4981
- includeBrightsy: isBrightsyConnected(),
5238
+ includeBrightsy: shouldInjectBrightsyMcp(thread, {
5239
+ orchestrator: isOrchestrator
5240
+ }),
4982
5241
  orchestratorThreadId: isOrchestrator ? thread.id : null
4983
5242
  });
4984
5243
  const injectedBrightsyNames = injectedServers.filter((s) => s.name === "brightsy" || s.name.startsWith("brightsy_")).map((s) => s.name);
@@ -5152,9 +5411,14 @@ async function listCodexModels() {
5152
5411
  if (cachedCodexModels && now - cachedCodexModels.at < CODEX_MODEL_CACHE_MS) {
5153
5412
  return cachedCodexModels.models;
5154
5413
  }
5155
- const which = await run("which", ["codex"], { reject: false });
5156
- if (which.exitCode !== 0) return FALLBACK_CODEX_MODELS;
5157
- const listed = await run("codex", ["debug", "models"], { reject: false });
5414
+ const codex = resolveAgentExecutable("codex");
5415
+ if (codex === "codex") {
5416
+ const which = await run("which", ["codex"], { reject: false });
5417
+ if (which.exitCode !== 0) return FALLBACK_CODEX_MODELS;
5418
+ } else if (!(0, import_node_fs13.existsSync)(codex)) {
5419
+ return FALLBACK_CODEX_MODELS;
5420
+ }
5421
+ const listed = await run(codex, ["debug", "models"], { reject: false });
5158
5422
  if (listed.exitCode !== 0 || !listed.stdout.trim()) {
5159
5423
  return cachedCodexModels?.models ?? FALLBACK_CODEX_MODELS;
5160
5424
  }
@@ -5193,8 +5457,8 @@ function codexConfigHasNetworkAccess() {
5193
5457
  (0, import_node_path11.join)((0, import_node_os7.homedir)(), ".config", "codex", "config.toml")
5194
5458
  ];
5195
5459
  for (const path of candidates) {
5196
- if (!(0, import_node_fs11.existsSync)(path)) continue;
5197
- const text = (0, import_node_fs11.readFileSync)(path, "utf8");
5460
+ if (!(0, import_node_fs13.existsSync)(path)) continue;
5461
+ const text = (0, import_node_fs13.readFileSync)(path, "utf8");
5198
5462
  if (/network_access\s*=\s*true/.test(text)) return true;
5199
5463
  }
5200
5464
  return false;
@@ -5227,21 +5491,23 @@ function asRecord(value) {
5227
5491
  }
5228
5492
  function codexLooksAuthenticated() {
5229
5493
  const authPath = (0, import_node_path11.join)((0, import_node_os7.homedir)(), ".codex", "auth.json");
5230
- if (!(0, import_node_fs11.existsSync)(authPath)) return false;
5494
+ if (!(0, import_node_fs13.existsSync)(authPath)) return false;
5231
5495
  try {
5232
- return (0, import_node_fs11.statSync)(authPath).size > 2;
5496
+ return (0, import_node_fs13.statSync)(authPath).size > 2;
5233
5497
  } catch {
5234
5498
  return false;
5235
5499
  }
5236
5500
  }
5237
- var import_node_fs11, import_node_os7, import_node_path11, CODEX_PROMPT_ARG_MAX, FALLBACK_CODEX_MODELS, cachedCodexModels, CODEX_MODEL_CACHE_MS, codexAdapter;
5501
+ var import_node_fs13, import_node_os7, import_node_path11, CODEX_PROMPT_ARG_MAX, FALLBACK_CODEX_MODELS, cachedCodexModels, CODEX_MODEL_CACHE_MS, codexAdapter;
5238
5502
  var init_codex = __esm({
5239
5503
  "src/agents/codex.ts"() {
5240
5504
  "use strict";
5241
- import_node_fs11 = require("fs");
5505
+ import_node_fs13 = require("fs");
5242
5506
  import_node_os7 = require("os");
5243
5507
  import_node_path11 = require("path");
5244
5508
  init_run();
5509
+ init_app_settings();
5510
+ init_global_workspace();
5245
5511
  init_error_detail();
5246
5512
  init_injected_mcp();
5247
5513
  init_turn_input();
@@ -5259,16 +5525,30 @@ var init_codex = __esm({
5259
5525
  codexAdapter = {
5260
5526
  kind: "codex",
5261
5527
  async detect() {
5262
- const which = await run("which", ["codex"], { reject: false, timeoutMs: 3e3 });
5263
- if (which.exitCode !== 0) {
5264
- return {
5265
- agent: "codex",
5266
- installed: false,
5267
- authenticated: false,
5268
- linearMcp: false,
5269
- warnings: [],
5270
- reason: "codex CLI not found on PATH"
5271
- };
5528
+ const codex = resolveAgentExecutable("codex");
5529
+ if (codex !== "codex") {
5530
+ if (!(0, import_node_fs13.existsSync)(codex)) {
5531
+ return {
5532
+ agent: "codex",
5533
+ installed: false,
5534
+ authenticated: false,
5535
+ linearMcp: false,
5536
+ warnings: [],
5537
+ reason: `Codex executable not found: ${codex}`
5538
+ };
5539
+ }
5540
+ } else {
5541
+ const which = await run("which", ["codex"], { reject: false, timeoutMs: 3e3 });
5542
+ if (which.exitCode !== 0) {
5543
+ return {
5544
+ agent: "codex",
5545
+ installed: false,
5546
+ authenticated: false,
5547
+ linearMcp: false,
5548
+ warnings: [],
5549
+ reason: "codex CLI not found on PATH"
5550
+ };
5551
+ }
5272
5552
  }
5273
5553
  const authenticated = codexLooksAuthenticated();
5274
5554
  const linearMcp = false;
@@ -5288,22 +5568,24 @@ var init_codex = __esm({
5288
5568
  };
5289
5569
  },
5290
5570
  async buildTurn(thread, input) {
5291
- const prompt = flattenTurnInput(input);
5292
5571
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
5572
+ const prompt = flattenTurnInput(dropCachedPrefixOnResume(input, sessionId));
5293
5573
  const useStdin = prompt.length > CODEX_PROMPT_ARG_MAX;
5294
5574
  const promptArg = useStdin ? "-" : prompt;
5295
5575
  if (process.env.SIDEBOARD_DEBUG_CODEX_TURN === "1") {
5296
- const { cachedPrefix } = normalizeTurnInput(input);
5297
5576
  console.error(
5298
- `[sideboard/codex] promptChars=${prompt.length} resumed=${Boolean(sessionId)} stdin=${useStdin} hasPrefix=${Boolean(cachedPrefix)}`
5577
+ `[sideboard/codex] promptChars=${prompt.length} resumed=${Boolean(sessionId)} stdin=${useStdin} hasPrefix=${prompt.includes("Current request:")}`
5299
5578
  );
5300
5579
  }
5301
5580
  const mode = permissionMode(thread);
5302
5581
  const model = thread.model?.trim();
5582
+ const isOrchestrator = isOrchestratorThread(thread);
5303
5583
  const injected = await buildInjectedMcpServers({
5304
5584
  includeSideboard: true,
5305
- includeBrightsy: isBrightsyConnected(),
5306
- orchestratorThreadId: thread.sourceType === "orchestration" ? thread.id : null
5585
+ includeBrightsy: shouldInjectBrightsyMcp(thread, {
5586
+ orchestrator: isOrchestrator
5587
+ }),
5588
+ orchestratorThreadId: isOrchestrator ? thread.id : null
5307
5589
  });
5308
5590
  const mcpOverrides = toCodexMcpConfigArgs(injected);
5309
5591
  const execOpts = [
@@ -5323,7 +5605,7 @@ var init_codex = __esm({
5323
5605
  ];
5324
5606
  const args = sessionId ? ["exec", ...execOpts, "resume", sessionId, promptArg] : ["exec", ...execOpts, promptArg];
5325
5607
  return {
5326
- file: "codex",
5608
+ file: resolveAgentExecutable("codex"),
5327
5609
  args,
5328
5610
  cwd: thread.worktreePath,
5329
5611
  stdin: useStdin ? `${prompt}
@@ -5419,16 +5701,17 @@ var init_codex = __esm({
5419
5701
  return cached;
5420
5702
  },
5421
5703
  async buildAttach(thread) {
5704
+ const codex = resolveAgentExecutable("codex");
5422
5705
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
5423
5706
  if (sessionId) {
5424
5707
  return {
5425
- file: "codex",
5708
+ file: codex,
5426
5709
  args: ["exec", "--cd", thread.worktreePath, "resume", sessionId],
5427
5710
  cwd: thread.worktreePath
5428
5711
  };
5429
5712
  }
5430
5713
  return {
5431
- file: "codex",
5714
+ file: codex,
5432
5715
  args: ["--cd", thread.worktreePath],
5433
5716
  cwd: thread.worktreePath
5434
5717
  };
@@ -5436,7 +5719,7 @@ var init_codex = __esm({
5436
5719
  async listLinearIssues(_repoPath) {
5437
5720
  const prompt = "List my assigned Linear issues as JSON array only: id, identifier, title, url, labels.";
5438
5721
  const { stdout, exitCode } = await run(
5439
- "codex",
5722
+ resolveAgentExecutable("codex"),
5440
5723
  [
5441
5724
  "exec",
5442
5725
  "--json",
@@ -5699,21 +5982,22 @@ function cursorRunnerPath() {
5699
5982
  (0, import_node_path12.join)(root, "src", "agents", "cursor-runner.ts")
5700
5983
  ];
5701
5984
  for (const candidate of candidates) {
5702
- if ((0, import_node_fs12.existsSync)(candidate)) return candidate;
5985
+ if ((0, import_node_fs14.existsSync)(candidate)) return candidate;
5703
5986
  }
5704
5987
  return candidates[0];
5705
5988
  }
5706
- var import_node_fs12, import_node_module2, import_node_path12, import_node_url2, import_sdk, import_meta2, FALLBACK_CURSOR_MODELS, cachedModels, MODEL_CACHE_MS, cursorAdapter;
5989
+ var import_node_fs14, import_node_module2, import_node_path12, import_node_url2, import_sdk, import_meta2, FALLBACK_CURSOR_MODELS, cachedModels, MODEL_CACHE_MS, cursorAdapter;
5707
5990
  var init_cursor = __esm({
5708
5991
  "src/agents/cursor.ts"() {
5709
5992
  "use strict";
5710
- import_node_fs12 = require("fs");
5993
+ import_node_fs14 = require("fs");
5711
5994
  import_node_module2 = require("module");
5712
5995
  import_node_path12 = require("path");
5713
5996
  import_node_url2 = require("url");
5714
5997
  import_sdk = require("@cursor/sdk");
5715
5998
  init_run();
5716
5999
  init_app_settings();
6000
+ init_global_workspace();
5717
6001
  init_cursor_events();
5718
6002
  init_injected_mcp();
5719
6003
  init_node_launch();
@@ -5750,13 +6034,16 @@ var init_cursor = __esm({
5750
6034
  };
5751
6035
  },
5752
6036
  async buildTurn(thread, input) {
5753
- const prompt = flattenTurnInput(input);
5754
6037
  const agentId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
6038
+ const prompt = flattenTurnInput(dropCachedPrefixOnResume(input, agentId));
5755
6039
  const apiKey = resolveCursorApiKey() || void 0;
6040
+ const isOrchestrator = isOrchestratorThread(thread);
5756
6041
  const injected = await buildInjectedMcpServers({
5757
6042
  includeSideboard: true,
5758
- includeBrightsy: isBrightsyConnected(),
5759
- orchestratorThreadId: thread.sourceType === "orchestration" ? thread.id : null
6043
+ includeBrightsy: shouldInjectBrightsyMcp(thread, {
6044
+ orchestrator: isOrchestrator
6045
+ }),
6046
+ orchestratorThreadId: isOrchestrator ? thread.id : null
5760
6047
  });
5761
6048
  const mcpServers = toCursorMcpServers(injected);
5762
6049
  const req = {
@@ -5827,9 +6114,14 @@ async function listOpencodeModels() {
5827
6114
  if (cachedOpencodeModels && now - cachedOpencodeModels.at < OPENCODE_MODEL_CACHE_MS) {
5828
6115
  return cachedOpencodeModels.models;
5829
6116
  }
5830
- const which = await run("which", ["opencode"], { reject: false });
5831
- if (which.exitCode !== 0) return FALLBACK_OPENCODE_MODELS;
5832
- const listed = await run("opencode", ["models"], { reject: false });
6117
+ const opencode = resolveAgentExecutable("opencode");
6118
+ if (opencode === "opencode") {
6119
+ const which = await run("which", ["opencode"], { reject: false });
6120
+ if (which.exitCode !== 0) return FALLBACK_OPENCODE_MODELS;
6121
+ } else if (!(0, import_node_fs15.existsSync)(opencode)) {
6122
+ return FALLBACK_OPENCODE_MODELS;
6123
+ }
6124
+ const listed = await run(opencode, ["models"], { reject: false });
5833
6125
  if (listed.exitCode !== 0 || !listed.stdout.trim()) {
5834
6126
  return cachedOpencodeModels?.models ?? FALLBACK_OPENCODE_MODELS;
5835
6127
  }
@@ -5856,11 +6148,14 @@ function usageFromOpencode(tokens) {
5856
6148
  cacheWriteTokens: tokens.cache?.write ? Number(tokens.cache.write) : void 0
5857
6149
  };
5858
6150
  }
5859
- var FALLBACK_OPENCODE_MODELS, cachedOpencodeModels, OPENCODE_MODEL_CACHE_MS, opencodeAdapter;
6151
+ var import_node_fs15, FALLBACK_OPENCODE_MODELS, cachedOpencodeModels, OPENCODE_MODEL_CACHE_MS, opencodeAdapter;
5860
6152
  var init_opencode = __esm({
5861
6153
  "src/agents/opencode.ts"() {
5862
6154
  "use strict";
6155
+ import_node_fs15 = require("fs");
5863
6156
  init_run();
6157
+ init_app_settings();
6158
+ init_global_workspace();
5864
6159
  init_error_detail();
5865
6160
  init_injected_mcp();
5866
6161
  init_turn_input();
@@ -5881,20 +6176,34 @@ var init_opencode = __esm({
5881
6176
  opencodeAdapter = {
5882
6177
  kind: "opencode",
5883
6178
  async detect() {
5884
- const which = await run("which", ["opencode"], { reject: false });
5885
- if (which.exitCode !== 0) {
5886
- return {
5887
- agent: "opencode",
5888
- installed: false,
5889
- authenticated: false,
5890
- linearMcp: false,
5891
- warnings: [],
5892
- reason: "opencode CLI not found on PATH"
5893
- };
6179
+ const opencode = resolveAgentExecutable("opencode");
6180
+ if (opencode !== "opencode") {
6181
+ if (!(0, import_node_fs15.existsSync)(opencode)) {
6182
+ return {
6183
+ agent: "opencode",
6184
+ installed: false,
6185
+ authenticated: false,
6186
+ linearMcp: false,
6187
+ warnings: [],
6188
+ reason: `OpenCode executable not found: ${opencode}`
6189
+ };
6190
+ }
6191
+ } else {
6192
+ const which = await run("which", ["opencode"], { reject: false });
6193
+ if (which.exitCode !== 0) {
6194
+ return {
6195
+ agent: "opencode",
6196
+ installed: false,
6197
+ authenticated: false,
6198
+ linearMcp: false,
6199
+ warnings: [],
6200
+ reason: "opencode CLI not found on PATH"
6201
+ };
6202
+ }
5894
6203
  }
5895
- const auth = await run("opencode", ["auth", "list"], { reject: false });
6204
+ const auth = await run(opencode, ["auth", "list"], { reject: false });
5896
6205
  const authenticated = auth.exitCode === 0 && auth.stdout.trim().length > 0;
5897
- const mcp = await run("opencode", ["mcp", "list"], { reject: false });
6206
+ const mcp = await run(opencode, ["mcp", "list"], { reject: false });
5898
6207
  const linearMcp = /linear/i.test(mcp.stdout + mcp.stderr);
5899
6208
  return {
5900
6209
  agent: "opencode",
@@ -5906,8 +6215,8 @@ var init_opencode = __esm({
5906
6215
  };
5907
6216
  },
5908
6217
  async buildTurn(thread, input) {
5909
- const prompt = flattenTurnInput(input);
5910
6218
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
6219
+ const prompt = flattenTurnInput(dropCachedPrefixOnResume(input, sessionId));
5911
6220
  const mode = permissionMode(thread);
5912
6221
  const model = thread.model?.trim();
5913
6222
  const args = [
@@ -5923,14 +6232,17 @@ var init_opencode = __esm({
5923
6232
  if (model) {
5924
6233
  args.push("--model", model);
5925
6234
  }
6235
+ const isOrchestrator = isOrchestratorThread(thread);
5926
6236
  const injected = await buildInjectedMcpServers({
5927
6237
  includeSideboard: true,
5928
- includeBrightsy: isBrightsyConnected(),
5929
- orchestratorThreadId: thread.sourceType === "orchestration" ? thread.id : null
6238
+ includeBrightsy: shouldInjectBrightsyMcp(thread, {
6239
+ orchestrator: isOrchestrator
6240
+ }),
6241
+ orchestratorThreadId: isOrchestrator ? thread.id : null
5930
6242
  });
5931
6243
  const mcpContent = injected.length > 0 ? toOpencodeMcpConfigContent(injected) : null;
5932
6244
  return {
5933
- file: "opencode",
6245
+ file: resolveAgentExecutable("opencode"),
5934
6246
  args,
5935
6247
  cwd: thread.worktreePath,
5936
6248
  // `opencode run` treats non-TTY stdin as the message body when no positional
@@ -6006,7 +6318,7 @@ var init_opencode = __esm({
6006
6318
  async resolveSessionId(worktreePath, cached) {
6007
6319
  const cachedId = cached?.trim() || null;
6008
6320
  const listed = await run(
6009
- "opencode",
6321
+ resolveAgentExecutable("opencode"),
6010
6322
  ["session", "list", "--format", "json"],
6011
6323
  { cwd: worktreePath, reject: false }
6012
6324
  );
@@ -6037,7 +6349,7 @@ var init_opencode = __esm({
6037
6349
  const args = ["--dir", thread.worktreePath];
6038
6350
  if (sessionId) args.push("--session", sessionId);
6039
6351
  return {
6040
- file: "opencode",
6352
+ file: resolveAgentExecutable("opencode"),
6041
6353
  args,
6042
6354
  cwd: thread.worktreePath,
6043
6355
  env: {
@@ -6048,7 +6360,7 @@ var init_opencode = __esm({
6048
6360
  async listLinearIssues(_repoPath) {
6049
6361
  const prompt = "List my assigned Linear issues as JSON array only: id, identifier, title, url, labels.";
6050
6362
  const { stdout, exitCode } = await run(
6051
- "opencode",
6363
+ resolveAgentExecutable("opencode"),
6052
6364
  ["run", prompt, "--format", "json"],
6053
6365
  {
6054
6366
  reject: false,
@@ -6281,9 +6593,30 @@ function getAgentSetupInfo(agent) {
6281
6593
  function listAgentSetupInfo() {
6282
6594
  return Object.values(SETUP);
6283
6595
  }
6284
- async function openInSystemTerminal(command) {
6285
- ensureAgentPath();
6596
+ function resolveLoginCommand(agent) {
6597
+ const info = getAgentSetupInfo(agent);
6598
+ if (!info.loginCommand) return null;
6599
+ if (!CLI_LOGIN_AGENTS.has(agent)) return info.loginCommand;
6600
+ const exe = resolveAgentExecutable(agent);
6601
+ return info.loginCommand.replace(/^\S+/, exe);
6602
+ }
6603
+ async function prepareTerminalCommand(command) {
6604
+ enrichPathWithNpmGlobalBin();
6286
6605
  const trimmed = command.trim();
6606
+ if (!trimmed) return trimmed;
6607
+ if (/^(export\s+PATH=|PATH=)/.test(trimmed)) return trimmed;
6608
+ const match = /^(\S+)(\s[\s\S]*)?$/.exec(trimmed);
6609
+ const bin = match?.[1];
6610
+ let whichPath = null;
6611
+ if (bin && !bin.includes("/") && !bin.includes("\\")) {
6612
+ const which = await run("which", [bin], { reject: false, timeoutMs: 3e3 });
6613
+ whichPath = which.exitCode === 0 ? which.stdout.trim() : null;
6614
+ }
6615
+ const resolved = resolveCommandBinarySync(trimmed, whichPath);
6616
+ return withExportedPath(resolved, process.env.PATH ?? "");
6617
+ }
6618
+ async function openInSystemTerminal(command) {
6619
+ const trimmed = (await prepareTerminalCommand(command)).trim();
6287
6620
  if (!trimmed) throw new Error("Command is empty");
6288
6621
  if (process.platform === "darwin") {
6289
6622
  const escaped = trimmed.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
@@ -6344,7 +6677,7 @@ async function installAgent(agent) {
6344
6677
  message: `Opened Terminal to run: ${info.installCommand}`
6345
6678
  };
6346
6679
  }
6347
- ensureAgentPath();
6680
+ enrichPathWithNpmGlobalBin();
6348
6681
  const result = await run("npm", ["install", "-g", info.npmPackage], { reject: false });
6349
6682
  const ok = result.exitCode === 0;
6350
6683
  if (!ok) {
@@ -6370,36 +6703,46 @@ async function installAgent(agent) {
6370
6703
  };
6371
6704
  }
6372
6705
  }
6706
+ enrichPathWithNpmGlobalBin();
6707
+ const cliBin = agent === "cursor" ? null : CLI_BIN[agent];
6708
+ let binPath = null;
6709
+ if (cliBin) {
6710
+ const which = await run("which", [cliBin], { reject: false, timeoutMs: 3e3 });
6711
+ binPath = which.exitCode === 0 ? which.stdout.trim().split(/\r?\n/).find(Boolean) ?? null : null;
6712
+ }
6373
6713
  return {
6374
6714
  ok: true,
6375
6715
  command: info.installCommand,
6376
6716
  exitCode: 0,
6377
6717
  stdout: result.stdout,
6378
6718
  stderr: result.stderr,
6379
- message: `Installed ${info.npmPackage}`
6719
+ message: binPath ? `Installed ${info.npmPackage} \u2192 ${binPath}` : `Installed ${info.npmPackage} (CLI not yet on PATH \u2014 Log in still exports Electron\u2019s PATH into Terminal)`
6380
6720
  };
6381
6721
  }
6382
6722
  async function loginAgent(agent) {
6383
6723
  const info = getAgentSetupInfo(agent);
6384
- if (!info.loginCommand) {
6724
+ const loginCommand = resolveLoginCommand(agent);
6725
+ if (!loginCommand) {
6385
6726
  return {
6386
6727
  ok: true,
6387
6728
  message: info.kind === "bundled-sdk" || info.kind === "api-key" ? info.summary : `No login command for ${agent}`
6388
6729
  };
6389
6730
  }
6390
- await openInSystemTerminal(info.loginCommand);
6731
+ const prepared = await prepareTerminalCommand(loginCommand);
6732
+ await openInSystemTerminal(prepared);
6391
6733
  return {
6392
6734
  ok: true,
6393
6735
  openedTerminal: true,
6394
- command: info.loginCommand,
6395
- message: `Opened Terminal to run: ${info.loginCommand}`
6736
+ command: prepared,
6737
+ message: `Opened Terminal to run: ${prepared}`
6396
6738
  };
6397
6739
  }
6398
- var SETUP;
6740
+ var SETUP, CLI_BIN, CLI_LOGIN_AGENTS;
6399
6741
  var init_install = __esm({
6400
6742
  "src/agents/install.ts"() {
6401
6743
  "use strict";
6402
6744
  init_run();
6745
+ init_app_settings();
6403
6746
  init_path();
6404
6747
  SETUP = {
6405
6748
  claude: {
@@ -6448,6 +6791,18 @@ var init_install = __esm({
6448
6791
  npmPackage: "@brightsy/cli"
6449
6792
  }
6450
6793
  };
6794
+ CLI_BIN = {
6795
+ claude: "claude",
6796
+ codex: "codex",
6797
+ opencode: "opencode",
6798
+ brightsy: "brightsy"
6799
+ };
6800
+ CLI_LOGIN_AGENTS = /* @__PURE__ */ new Set([
6801
+ "claude",
6802
+ "codex",
6803
+ "opencode",
6804
+ "brightsy"
6805
+ ]);
6451
6806
  }
6452
6807
  });
6453
6808
 
@@ -6467,6 +6822,7 @@ __export(agents_exports, {
6467
6822
  cursorSdkMessageToEvents: () => cursorSdkMessageToEvents,
6468
6823
  decodeBrightsyTarget: () => decodeBrightsyTarget,
6469
6824
  encodeBrightsyTarget: () => encodeBrightsyTarget,
6825
+ enrichPathWithNpmGlobalBin: () => enrichPathWithNpmGlobalBin,
6470
6826
  ensureAgentPath: () => ensureAgentPath,
6471
6827
  getAdapter: () => getAdapter,
6472
6828
  getAgentSetupInfo: () => getAgentSetupInfo,
@@ -6486,8 +6842,13 @@ __export(agents_exports, {
6486
6842
  parseCursorRunnerLine: () => parseCursorRunnerLine,
6487
6843
  parseSessionQuotaResetAt: () => parseSessionQuotaResetAt,
6488
6844
  permissionMode: () => permissionMode,
6845
+ posixShellSingleQuote: () => posixShellSingleQuote,
6846
+ prepareTerminalCommand: () => prepareTerminalCommand,
6847
+ resolveCommandBinarySync: () => resolveCommandBinarySync,
6489
6848
  resolveCursorModelId: () => resolveCursorModelId,
6490
- resolveQuotaFallbackAgent: () => resolveQuotaFallbackAgent
6849
+ resolveLoginCommand: () => resolveLoginCommand,
6850
+ resolveQuotaFallbackAgent: () => resolveQuotaFallbackAgent,
6851
+ withExportedPath: () => withExportedPath
6491
6852
  });
6492
6853
  function getAdapter(kind) {
6493
6854
  return adapters[kind];
@@ -6545,31 +6906,31 @@ function removedWorkspacesFile() {
6545
6906
  }
6546
6907
  function readAll() {
6547
6908
  const path = workspacesFile();
6548
- if (!(0, import_node_fs16.existsSync)(path)) return [];
6909
+ if (!(0, import_node_fs19.existsSync)(path)) return [];
6549
6910
  try {
6550
- const raw = JSON.parse((0, import_node_fs16.readFileSync)(path, "utf8"));
6911
+ const raw = JSON.parse((0, import_node_fs19.readFileSync)(path, "utf8"));
6551
6912
  return Array.isArray(raw) ? raw : [];
6552
6913
  } catch {
6553
6914
  return [];
6554
6915
  }
6555
6916
  }
6556
6917
  function writeAll(list) {
6557
- (0, import_node_fs16.mkdirSync)(appDataDir(), { recursive: true });
6558
- (0, import_node_fs16.writeFileSync)(workspacesFile(), JSON.stringify(list, null, 2), "utf8");
6918
+ (0, import_node_fs19.mkdirSync)(appDataDir(), { recursive: true });
6919
+ (0, import_node_fs19.writeFileSync)(workspacesFile(), JSON.stringify(list, null, 2), "utf8");
6559
6920
  }
6560
6921
  function readRemoved() {
6561
6922
  const path = removedWorkspacesFile();
6562
- if (!(0, import_node_fs16.existsSync)(path)) return /* @__PURE__ */ new Set();
6923
+ if (!(0, import_node_fs19.existsSync)(path)) return /* @__PURE__ */ new Set();
6563
6924
  try {
6564
- const raw = JSON.parse((0, import_node_fs16.readFileSync)(path, "utf8"));
6925
+ const raw = JSON.parse((0, import_node_fs19.readFileSync)(path, "utf8"));
6565
6926
  return new Set(Array.isArray(raw) ? raw.filter((p) => typeof p === "string") : []);
6566
6927
  } catch {
6567
6928
  return /* @__PURE__ */ new Set();
6568
6929
  }
6569
6930
  }
6570
6931
  function writeRemoved(paths) {
6571
- (0, import_node_fs16.mkdirSync)(appDataDir(), { recursive: true });
6572
- (0, import_node_fs16.writeFileSync)(removedWorkspacesFile(), JSON.stringify([...paths].sort(), null, 2), "utf8");
6932
+ (0, import_node_fs19.mkdirSync)(appDataDir(), { recursive: true });
6933
+ (0, import_node_fs19.writeFileSync)(removedWorkspacesFile(), JSON.stringify([...paths].sort(), null, 2), "utf8");
6573
6934
  }
6574
6935
  function rememberRemoved(repoPath) {
6575
6936
  const next = readRemoved();
@@ -6592,7 +6953,7 @@ function listWorkspaces() {
6592
6953
  async function addWorkspace(repoPath) {
6593
6954
  const root = await resolveRepoRoot(repoPath);
6594
6955
  if (!root || root === "/") throw new Error(`Invalid repo path: ${repoPath}`);
6595
- if (!(0, import_node_fs16.existsSync)(root)) throw new Error(`Repo not found: ${root}`);
6956
+ if (!(0, import_node_fs19.existsSync)(root)) throw new Error(`Repo not found: ${root}`);
6596
6957
  forgetRemoved(root);
6597
6958
  await ensureGhPreferOrigin(root);
6598
6959
  const current = readAll();
@@ -6622,7 +6983,7 @@ function syncWorkspacesFromThreads(repoPaths) {
6622
6983
  if (!path || path === "/" || isGlobalRepoPath(path) || byPath.has(path) || removed.has(path)) {
6623
6984
  continue;
6624
6985
  }
6625
- if (!(0, import_node_fs16.existsSync)(path)) continue;
6986
+ if (!(0, import_node_fs19.existsSync)(path)) continue;
6626
6987
  const ws = {
6627
6988
  path,
6628
6989
  name: (0, import_node_path16.basename)(path),
@@ -6635,11 +6996,11 @@ function syncWorkspacesFromThreads(repoPaths) {
6635
6996
  if (dirty) writeAll(next);
6636
6997
  return next.sort((a, b) => a.name.localeCompare(b.name));
6637
6998
  }
6638
- var import_node_fs16, import_node_path16;
6999
+ var import_node_fs19, import_node_path16;
6639
7000
  var init_workspaces = __esm({
6640
7001
  "src/store/workspaces.ts"() {
6641
7002
  "use strict";
6642
- import_node_fs16 = require("fs");
7003
+ import_node_fs19 = require("fs");
6643
7004
  import_node_path16 = require("path");
6644
7005
  init_paths();
6645
7006
  init_global_workspace();
@@ -6718,17 +7079,17 @@ __export(plan_file_exports, {
6718
7079
  });
6719
7080
  function ensureAttachmentsGitignore2(worktreePath) {
6720
7081
  const gitignoreAbs = (0, import_node_path24.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
6721
- if ((0, import_node_fs26.existsSync)(gitignoreAbs)) return;
6722
- (0, import_node_fs26.mkdirSync)((0, import_node_path24.dirname)(gitignoreAbs), { recursive: true });
6723
- (0, import_node_fs26.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
7082
+ if ((0, import_node_fs29.existsSync)(gitignoreAbs)) return;
7083
+ (0, import_node_fs29.mkdirSync)((0, import_node_path24.dirname)(gitignoreAbs), { recursive: true });
7084
+ (0, import_node_fs29.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
6724
7085
  }
6725
7086
  function planFileAbs(worktreePath) {
6726
7087
  return (0, import_node_path24.join)(worktreePath, PLAN_FILE_REL);
6727
7088
  }
6728
7089
  function readTextIfPresent2(abs) {
6729
- if (!(0, import_node_fs26.existsSync)(abs)) return null;
7090
+ if (!(0, import_node_fs29.existsSync)(abs)) return null;
6730
7091
  try {
6731
- const content = (0, import_node_fs26.readFileSync)(abs, "utf8");
7092
+ const content = (0, import_node_fs29.readFileSync)(abs, "utf8");
6732
7093
  return content.trim() ? content : null;
6733
7094
  } catch {
6734
7095
  return null;
@@ -6740,16 +7101,16 @@ function readPlanFile(worktreePath) {
6740
7101
  function writePlanFile(worktreePath, content) {
6741
7102
  ensureAttachmentsGitignore2(worktreePath);
6742
7103
  const abs = planFileAbs(worktreePath);
6743
- (0, import_node_fs26.mkdirSync)((0, import_node_path24.dirname)(abs), { recursive: true });
7104
+ (0, import_node_fs29.mkdirSync)((0, import_node_path24.dirname)(abs), { recursive: true });
6744
7105
  const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
6745
- (0, import_node_fs26.writeFileSync)(abs, body, "utf8");
7106
+ (0, import_node_fs29.writeFileSync)(abs, body, "utf8");
6746
7107
  return PLAN_FILE_REL;
6747
7108
  }
6748
- var import_node_fs26, import_node_path24;
7109
+ var import_node_fs29, import_node_path24;
6749
7110
  var init_plan_file = __esm({
6750
7111
  "src/plan/plan-file.ts"() {
6751
7112
  "use strict";
6752
- import_node_fs26 = require("fs");
7113
+ import_node_fs29 = require("fs");
6753
7114
  import_node_path24 = require("path");
6754
7115
  init_workspace_scratch();
6755
7116
  init_plan_present();
@@ -6766,9 +7127,9 @@ function recoverFinishedCursorRun(opts) {
6766
7127
  const agentId = opts.agentId.trim();
6767
7128
  if (!agentId) return null;
6768
7129
  const runsPath = (0, import_node_path25.join)(appDataDir(), "cursor-sdk-store", "runs.ndjson");
6769
- if (!(0, import_node_fs27.existsSync)(runsPath)) return null;
7130
+ if (!(0, import_node_fs30.existsSync)(runsPath)) return null;
6770
7131
  try {
6771
- const lines = (0, import_node_fs27.readFileSync)(runsPath, "utf8").split("\n");
7132
+ const lines = (0, import_node_fs30.readFileSync)(runsPath, "utf8").split("\n");
6772
7133
  let best = null;
6773
7134
  for (const line of lines) {
6774
7135
  const trimmed = line.trim();
@@ -6794,11 +7155,11 @@ function recoverFinishedCursorRun(opts) {
6794
7155
  return null;
6795
7156
  }
6796
7157
  }
6797
- var import_node_fs27, import_node_path25;
7158
+ var import_node_fs30, import_node_path25;
6798
7159
  var init_cursor_recover = __esm({
6799
7160
  "src/agents/cursor-recover.ts"() {
6800
7161
  "use strict";
6801
- import_node_fs27 = require("fs");
7162
+ import_node_fs30 = require("fs");
6802
7163
  import_node_path25 = require("path");
6803
7164
  init_paths();
6804
7165
  }
@@ -6812,7 +7173,7 @@ var import_node_path26 = require("path");
6812
7173
 
6813
7174
  // src/orchestrator/orchestrator.ts
6814
7175
  var import_node_events = require("events");
6815
- var import_node_fs28 = require("fs");
7176
+ var import_node_fs31 = require("fs");
6816
7177
  init_error_detail();
6817
7178
 
6818
7179
  // src/agents/spawn.ts
@@ -7187,8 +7548,22 @@ init_agents();
7187
7548
  init_worktree();
7188
7549
  init_stack();
7189
7550
 
7551
+ // src/git/pr-merge-archive.ts
7552
+ function normalizePrState(state) {
7553
+ return (state ?? "").trim().toUpperCase();
7554
+ }
7555
+ function shouldAutoArchiveOnPrMerge(opts) {
7556
+ if (!opts.autoArchiveEnabled) return false;
7557
+ if (opts.isGlobal) return false;
7558
+ if (opts.skipAutoArchiveOnMerge) return false;
7559
+ if (opts.threadStatus === "archived") return false;
7560
+ const next = normalizePrState(opts.nextPrState);
7561
+ if (next !== "MERGED") return false;
7562
+ return normalizePrState(opts.previousPrState) !== "MERGED";
7563
+ }
7564
+
7190
7565
  // src/hook/conductor.ts
7191
- var import_node_fs13 = require("fs");
7566
+ var import_node_fs16 = require("fs");
7192
7567
  var import_node_net = require("net");
7193
7568
  var import_node_path13 = require("path");
7194
7569
  var import_execa3 = require("execa");
@@ -7201,8 +7576,8 @@ function matchSimpleGlob(pattern, name) {
7201
7576
  }
7202
7577
  function readWorktreeInclude(repoPath) {
7203
7578
  const path = (0, import_node_path13.join)(repoPath, ".worktreeinclude");
7204
- if (!(0, import_node_fs13.existsSync)(path)) return [];
7205
- return (0, import_node_fs13.readFileSync)(path, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
7579
+ if (!(0, import_node_fs16.existsSync)(path)) return [];
7580
+ return (0, import_node_fs16.readFileSync)(path, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
7206
7581
  }
7207
7582
  function resolveFilesToCopy(repoPath) {
7208
7583
  const fromInclude = readWorktreeInclude(repoPath);
@@ -7212,7 +7587,7 @@ function resolveFilesToCopy(repoPath) {
7212
7587
  if (settings?.fileIncludeGlobs?.length) {
7213
7588
  const matched = [];
7214
7589
  try {
7215
- for (const entry of (0, import_node_fs13.readdirSync)(repoPath, { withFileTypes: true })) {
7590
+ for (const entry of (0, import_node_fs16.readdirSync)(repoPath, { withFileTypes: true })) {
7216
7591
  if (!entry.isFile()) continue;
7217
7592
  for (const glob of settings.fileIncludeGlobs) {
7218
7593
  if (matchSimpleGlob(glob, entry.name) || matchSimpleGlob((0, import_node_path13.basename)(glob), entry.name)) {
@@ -7227,7 +7602,7 @@ function resolveFilesToCopy(repoPath) {
7227
7602
  }
7228
7603
  const defaults = [];
7229
7604
  try {
7230
- for (const entry of (0, import_node_fs13.readdirSync)(repoPath, { withFileTypes: true })) {
7605
+ for (const entry of (0, import_node_fs16.readdirSync)(repoPath, { withFileTypes: true })) {
7231
7606
  if (entry.isFile() && entry.name.startsWith(".env")) {
7232
7607
  defaults.push(entry.name);
7233
7608
  }
@@ -7242,10 +7617,10 @@ function copyConfiguredFiles(repoPath, worktreePath) {
7242
7617
  const copied = [];
7243
7618
  for (const rel of patterns) {
7244
7619
  const src = (0, import_node_path13.join)(repoPath, rel);
7245
- if (!(0, import_node_fs13.existsSync)(src)) continue;
7620
+ if (!(0, import_node_fs16.existsSync)(src)) continue;
7246
7621
  const dest = (0, import_node_path13.join)(worktreePath, rel);
7247
- (0, import_node_fs13.mkdirSync)((0, import_node_path13.dirname)(dest), { recursive: true });
7248
- (0, import_node_fs13.copyFileSync)(src, dest);
7622
+ (0, import_node_fs16.mkdirSync)((0, import_node_path13.dirname)(dest), { recursive: true });
7623
+ (0, import_node_fs16.copyFileSync)(src, dest);
7249
7624
  copied.push(rel);
7250
7625
  }
7251
7626
  return copied;
@@ -7498,15 +7873,15 @@ async function startDevServer(repoPath, worktreePath, onLine, opts) {
7498
7873
  }
7499
7874
 
7500
7875
  // src/hook/cursor-worktrees.ts
7501
- var import_node_fs14 = require("fs");
7876
+ var import_node_fs17 = require("fs");
7502
7877
  var import_node_path14 = require("path");
7503
7878
  var import_execa4 = require("execa");
7504
7879
  var import_node_readline3 = require("readline");
7505
7880
  function loadCursorWorktreesJson(rootPath) {
7506
7881
  const path = (0, import_node_path14.join)(rootPath, ".cursor", "worktrees.json");
7507
- if (!(0, import_node_fs14.existsSync)(path)) return null;
7882
+ if (!(0, import_node_fs17.existsSync)(path)) return null;
7508
7883
  try {
7509
- return JSON.parse((0, import_node_fs14.readFileSync)(path, "utf8"));
7884
+ return JSON.parse((0, import_node_fs17.readFileSync)(path, "utf8"));
7510
7885
  } catch {
7511
7886
  return null;
7512
7887
  }
@@ -7564,7 +7939,7 @@ async function runCursorWorktreeSetup(repoPath, worktreePath, onLine) {
7564
7939
  }
7565
7940
 
7566
7941
  // src/git/orphan-cleanup.ts
7567
- var import_node_fs15 = require("fs");
7942
+ var import_node_fs18 = require("fs");
7568
7943
  var import_node_path15 = require("path");
7569
7944
  init_worktree();
7570
7945
  init_thread_store();
@@ -7580,9 +7955,9 @@ async function findOrphanWorktrees(repoPaths) {
7580
7955
  repoPaths?.length ? repoPaths : threads.map((t) => t.repoPath).filter(Boolean)
7581
7956
  );
7582
7957
  const homeRoot = sideboardWorkspacesDir();
7583
- if ((0, import_node_fs15.existsSync)(homeRoot)) {
7958
+ if ((0, import_node_fs18.existsSync)(homeRoot)) {
7584
7959
  try {
7585
- for (const entry of (0, import_node_fs15.readdirSync)(homeRoot, { withFileTypes: true })) {
7960
+ for (const entry of (0, import_node_fs18.readdirSync)(homeRoot, { withFileTypes: true })) {
7586
7961
  if (!entry.isDirectory()) continue;
7587
7962
  void entry;
7588
7963
  }
@@ -7592,7 +7967,7 @@ async function findOrphanWorktrees(repoPaths) {
7592
7967
  const orphans = [];
7593
7968
  const seen = /* @__PURE__ */ new Set();
7594
7969
  for (const repoPath of repos) {
7595
- if (!repoPath || !(0, import_node_fs15.existsSync)(repoPath)) continue;
7970
+ if (!repoPath || !(0, import_node_fs18.existsSync)(repoPath)) continue;
7596
7971
  try {
7597
7972
  const wts = await listWorktrees(repoPath);
7598
7973
  for (const wt of wts) {
@@ -7603,7 +7978,7 @@ async function findOrphanWorktrees(repoPaths) {
7603
7978
  seen.add(path);
7604
7979
  let mtimeMs = 0;
7605
7980
  try {
7606
- mtimeMs = (0, import_node_fs15.statSync)(path).mtimeMs;
7981
+ mtimeMs = (0, import_node_fs18.statSync)(path).mtimeMs;
7607
7982
  } catch {
7608
7983
  mtimeMs = 0;
7609
7984
  }
@@ -7613,16 +7988,16 @@ async function findOrphanWorktrees(repoPaths) {
7613
7988
  }
7614
7989
  try {
7615
7990
  const root = worktreesRoot(repoPath);
7616
- if ((0, import_node_fs15.existsSync)(root)) {
7617
- for (const entry of (0, import_node_fs15.readdirSync)(root, { withFileTypes: true })) {
7991
+ if ((0, import_node_fs18.existsSync)(root)) {
7992
+ for (const entry of (0, import_node_fs18.readdirSync)(root, { withFileTypes: true })) {
7618
7993
  if (!entry.isDirectory()) continue;
7619
7994
  const path = (0, import_node_path15.join)(root, entry.name).replace(/\/$/, "");
7620
7995
  if (known.has(path) || seen.has(path)) continue;
7621
- if (!(0, import_node_fs15.existsSync)((0, import_node_path15.join)(path, ".git"))) continue;
7996
+ if (!(0, import_node_fs18.existsSync)((0, import_node_path15.join)(path, ".git"))) continue;
7622
7997
  seen.add(path);
7623
7998
  let mtimeMs = 0;
7624
7999
  try {
7625
- mtimeMs = (0, import_node_fs15.statSync)(path).mtimeMs;
8000
+ mtimeMs = (0, import_node_fs18.statSync)(path).mtimeMs;
7626
8001
  } catch {
7627
8002
  mtimeMs = Date.now();
7628
8003
  }
@@ -7759,7 +8134,7 @@ async function applyThreadIntoMain(thread, opts) {
7759
8134
  }
7760
8135
 
7761
8136
  // src/git/clone-repo.ts
7762
- var import_node_fs17 = require("fs");
8137
+ var import_node_fs20 = require("fs");
7763
8138
  var import_node_path17 = require("path");
7764
8139
  var import_execa6 = require("execa");
7765
8140
  init_paths();
@@ -7775,7 +8150,7 @@ async function cloneRepoIntoSideboard(opts) {
7775
8150
  }
7776
8151
  name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
7777
8152
  const dest = (0, import_node_path17.join)(sideboardReposDir(), name);
7778
- if ((0, import_node_fs17.existsSync)(dest)) {
8153
+ if ((0, import_node_fs20.existsSync)(dest)) {
7779
8154
  const repoPath2 = await resolveRepoRoot(dest);
7780
8155
  const workspace2 = await ensureWorkspace(repoPath2);
7781
8156
  return { repoPath: repoPath2, workspace: workspace2 };
@@ -7795,13 +8170,14 @@ async function cloneRepoIntoSideboard(opts) {
7795
8170
  init_thread_store();
7796
8171
 
7797
8172
  // src/threads/create.ts
7798
- var import_node_fs18 = require("fs");
8173
+ var import_node_fs21 = require("fs");
7799
8174
 
7800
8175
  // src/detect/detect.ts
7801
8176
  init_agents();
7802
8177
  var REQUIRE_AGENT_TIMEOUT_MS = 8e3;
7803
8178
  async function requireAgent(agent, opts) {
7804
8179
  ensureAgentPath();
8180
+ enrichPathWithNpmGlobalBin();
7805
8181
  const status = await Promise.race([
7806
8182
  getAdapter(agent).detect(),
7807
8183
  new Promise((_, reject) => {
@@ -7839,7 +8215,7 @@ async function createThread(input, _onSetupLine) {
7839
8215
  });
7840
8216
  await requireAgent(resolved.agent);
7841
8217
  const repoPath = await resolveRepoRoot(input.repoPath);
7842
- if (!(0, import_node_fs18.existsSync)(repoPath)) {
8218
+ if (!(0, import_node_fs21.existsSync)(repoPath)) {
7843
8219
  throw new Error(`Repo not found: ${repoPath}`);
7844
8220
  }
7845
8221
  let sourceRef = input.sourceRef;
@@ -8226,6 +8602,8 @@ function worktreeBindingFrom(from) {
8226
8602
  parentThreadId: from.parentThreadId,
8227
8603
  prUrl: from.prUrl,
8228
8604
  prTitle: from.prTitle,
8605
+ prState: from.prState,
8606
+ skipAutoArchiveOnMerge: from.skipAutoArchiveOnMerge,
8229
8607
  stackId: from.stackId,
8230
8608
  stackLayer: from.stackLayer
8231
8609
  };
@@ -8300,7 +8678,7 @@ function forkChatTab(input) {
8300
8678
 
8301
8679
  // src/review/request-review.ts
8302
8680
  var import_node_crypto3 = require("crypto");
8303
- var import_node_fs19 = require("fs");
8681
+ var import_node_fs22 = require("fs");
8304
8682
  var import_node_path18 = require("path");
8305
8683
  init_global_workspace();
8306
8684
  init_thread_store();
@@ -8445,9 +8823,9 @@ function shouldRefreshReviewRequestTemplate(content) {
8445
8823
  return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
8446
8824
  }
8447
8825
  function readTextIfPresent(abs) {
8448
- if (!(0, import_node_fs19.existsSync)(abs)) return null;
8826
+ if (!(0, import_node_fs22.existsSync)(abs)) return null;
8449
8827
  try {
8450
- const content = (0, import_node_fs19.readFileSync)(abs, "utf8");
8828
+ const content = (0, import_node_fs22.readFileSync)(abs, "utf8");
8451
8829
  return content.trim() ? content : null;
8452
8830
  } catch {
8453
8831
  return null;
@@ -8455,9 +8833,9 @@ function readTextIfPresent(abs) {
8455
8833
  }
8456
8834
  function ensureAttachmentsGitignore(worktreePath) {
8457
8835
  const gitignoreAbs = (0, import_node_path18.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
8458
- if ((0, import_node_fs19.existsSync)(gitignoreAbs)) return;
8459
- (0, import_node_fs19.mkdirSync)((0, import_node_path18.dirname)(gitignoreAbs), { recursive: true });
8460
- (0, import_node_fs19.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
8836
+ if ((0, import_node_fs22.existsSync)(gitignoreAbs)) return;
8837
+ (0, import_node_fs22.mkdirSync)((0, import_node_path18.dirname)(gitignoreAbs), { recursive: true });
8838
+ (0, import_node_fs22.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
8461
8839
  }
8462
8840
  function resolveReviewGuidelines(worktreePath) {
8463
8841
  const repoAbs = (0, import_node_path18.join)(worktreePath, REPO_REVIEW_PATH);
@@ -8491,8 +8869,8 @@ function resolveReviewGuidelines(worktreePath) {
8491
8869
  };
8492
8870
  }
8493
8871
  ensureAttachmentsGitignore(worktreePath);
8494
- (0, import_node_fs19.mkdirSync)((0, import_node_path18.dirname)(localAbs), { recursive: true });
8495
- (0, import_node_fs19.writeFileSync)(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
8872
+ (0, import_node_fs22.mkdirSync)((0, import_node_path18.dirname)(localAbs), { recursive: true });
8873
+ (0, import_node_fs22.writeFileSync)(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
8496
8874
  return {
8497
8875
  path: REVIEW_REQUEST_PATH,
8498
8876
  name: REVIEW_REQUEST_NAME,
@@ -8691,8 +9069,8 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
8691
9069
  }
8692
9070
 
8693
9071
  // src/threads/adopt.ts
8694
- var import_node_child_process = require("child_process");
8695
- var import_node_fs20 = require("fs");
9072
+ var import_node_child_process2 = require("child_process");
9073
+ var import_node_fs23 = require("fs");
8696
9074
  var import_node_os8 = require("os");
8697
9075
  var import_node_path19 = require("path");
8698
9076
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
@@ -8717,21 +9095,21 @@ function mapAgentType(raw) {
8717
9095
  return null;
8718
9096
  }
8719
9097
  function resolveConductorCursorAgentId(workspacePath) {
8720
- if (!workspacePath || !(0, import_node_fs20.existsSync)(CURSOR_SDK_STORE)) return null;
9098
+ if (!workspacePath || !(0, import_node_fs23.existsSync)(CURSOR_SDK_STORE)) return null;
8721
9099
  const normalized = workspacePath.replace(/\/$/, "");
8722
9100
  let best = null;
8723
9101
  let hashes;
8724
9102
  try {
8725
- hashes = (0, import_node_fs20.readdirSync)(CURSOR_SDK_STORE);
9103
+ hashes = (0, import_node_fs23.readdirSync)(CURSOR_SDK_STORE);
8726
9104
  } catch {
8727
9105
  return null;
8728
9106
  }
8729
9107
  for (const hash of hashes) {
8730
9108
  const agentsFile = (0, import_node_path19.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
8731
- if (!(0, import_node_fs20.existsSync)(agentsFile)) continue;
9109
+ if (!(0, import_node_fs23.existsSync)(agentsFile)) continue;
8732
9110
  let text;
8733
9111
  try {
8734
- text = (0, import_node_fs20.readFileSync)(agentsFile, "utf8");
9112
+ text = (0, import_node_fs23.readFileSync)(agentsFile, "utf8");
8735
9113
  } catch {
8736
9114
  continue;
8737
9115
  }
@@ -8755,7 +9133,7 @@ function resolveConductorCursorAgentId(workspacePath) {
8755
9133
  return best?.agentId ?? null;
8756
9134
  }
8757
9135
  async function adoptThread(input) {
8758
- if (!(0, import_node_fs20.existsSync)(input.worktreePath)) {
9136
+ if (!(0, import_node_fs23.existsSync)(input.worktreePath)) {
8759
9137
  throw new Error(`Worktree not found: ${input.worktreePath}`);
8760
9138
  }
8761
9139
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -8779,18 +9157,18 @@ async function adoptThread(input) {
8779
9157
  return thread;
8780
9158
  }
8781
9159
  function listConductorWorkspaces() {
8782
- if (!(0, import_node_fs20.existsSync)(CONDUCTOR_DB)) {
9160
+ if (!(0, import_node_fs23.existsSync)(CONDUCTOR_DB)) {
8783
9161
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
8784
9162
  }
8785
- const tmp = (0, import_node_fs20.mkdtempSync)((0, import_node_path19.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
9163
+ const tmp = (0, import_node_fs23.mkdtempSync)((0, import_node_path19.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
8786
9164
  const snapshot = (0, import_node_path19.join)(tmp, "conductor.db");
8787
9165
  try {
8788
- (0, import_node_fs20.copyFileSync)(CONDUCTOR_DB, snapshot);
9166
+ (0, import_node_fs23.copyFileSync)(CONDUCTOR_DB, snapshot);
8789
9167
  for (const suffix of ["-wal", "-shm"]) {
8790
9168
  const src = `${CONDUCTOR_DB}${suffix}`;
8791
- if ((0, import_node_fs20.existsSync)(src)) {
9169
+ if ((0, import_node_fs23.existsSync)(src)) {
8792
9170
  try {
8793
- (0, import_node_fs20.copyFileSync)(src, `${snapshot}${suffix}`);
9171
+ (0, import_node_fs23.copyFileSync)(src, `${snapshot}${suffix}`);
8794
9172
  } catch {
8795
9173
  }
8796
9174
  }
@@ -8866,22 +9244,22 @@ function listConductorWorkspaces() {
8866
9244
  db.close();
8867
9245
  }
8868
9246
  } finally {
8869
- (0, import_node_fs20.rmSync)(tmp, { recursive: true, force: true });
9247
+ (0, import_node_fs23.rmSync)(tmp, { recursive: true, force: true });
8870
9248
  }
8871
9249
  }
8872
9250
  function importConductorWorkspace(workspaceId) {
8873
- if (!(0, import_node_fs20.existsSync)(CONDUCTOR_DB)) {
9251
+ if (!(0, import_node_fs23.existsSync)(CONDUCTOR_DB)) {
8874
9252
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
8875
9253
  }
8876
- const tmp = (0, import_node_fs20.mkdtempSync)((0, import_node_path19.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
9254
+ const tmp = (0, import_node_fs23.mkdtempSync)((0, import_node_path19.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
8877
9255
  const snapshot = (0, import_node_path19.join)(tmp, "conductor.db");
8878
9256
  try {
8879
- (0, import_node_fs20.copyFileSync)(CONDUCTOR_DB, snapshot);
9257
+ (0, import_node_fs23.copyFileSync)(CONDUCTOR_DB, snapshot);
8880
9258
  for (const suffix of ["-wal", "-shm"]) {
8881
9259
  const src = `${CONDUCTOR_DB}${suffix}`;
8882
- if ((0, import_node_fs20.existsSync)(src)) {
9260
+ if ((0, import_node_fs23.existsSync)(src)) {
8883
9261
  try {
8884
- (0, import_node_fs20.copyFileSync)(src, `${snapshot}${suffix}`);
9262
+ (0, import_node_fs23.copyFileSync)(src, `${snapshot}${suffix}`);
8885
9263
  } catch {
8886
9264
  }
8887
9265
  }
@@ -8899,7 +9277,7 @@ function importConductorWorkspace(workspaceId) {
8899
9277
  ).get(workspaceId);
8900
9278
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
8901
9279
  const worktreePath = String(row.workspacePath);
8902
- if (!(0, import_node_fs20.existsSync)(worktreePath)) {
9280
+ if (!(0, import_node_fs23.existsSync)(worktreePath)) {
8903
9281
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
8904
9282
  }
8905
9283
  let sessionId = null;
@@ -8939,7 +9317,7 @@ function importConductorWorkspace(workspaceId) {
8939
9317
  }
8940
9318
  let repoPath = worktreePath;
8941
9319
  try {
8942
- repoPath = (0, import_node_child_process.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
9320
+ repoPath = (0, import_node_child_process2.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
8943
9321
  cwd: worktreePath,
8944
9322
  encoding: "utf8"
8945
9323
  }).trim();
@@ -8962,7 +9340,7 @@ function importConductorWorkspace(workspaceId) {
8962
9340
  db.close();
8963
9341
  }
8964
9342
  } finally {
8965
- (0, import_node_fs20.rmSync)(tmp, { recursive: true, force: true });
9343
+ (0, import_node_fs23.rmSync)(tmp, { recursive: true, force: true });
8966
9344
  }
8967
9345
  }
8968
9346
  async function importConductorWorkspaceAsync(workspaceId) {
@@ -8970,7 +9348,7 @@ async function importConductorWorkspaceAsync(workspaceId) {
8970
9348
  }
8971
9349
 
8972
9350
  // src/threads/stack-layers.ts
8973
- var import_node_fs21 = require("fs");
9351
+ var import_node_fs24 = require("fs");
8974
9352
  init_run();
8975
9353
  init_stack();
8976
9354
  init_worktree();
@@ -9038,7 +9416,7 @@ async function openStackLayer(input, _onSetupLine) {
9038
9416
  let createdWorktree = false;
9039
9417
  const trees = await listWorktrees(repoPath);
9040
9418
  const checkedOut = trees.find((w) => w.branch === branchName);
9041
- if (checkedOut?.path && (0, import_node_fs21.existsSync)(checkedOut.path)) {
9419
+ if (checkedOut?.path && (0, import_node_fs24.existsSync)(checkedOut.path)) {
9042
9420
  if (input.reuseExistingWorktree !== false) {
9043
9421
  worktreePath = checkedOut.path;
9044
9422
  } else {
@@ -9180,7 +9558,7 @@ async function initStackFromThread(input, onSetupLine) {
9180
9558
  async function createPrStack(input, onSetupLine) {
9181
9559
  await requireAgent(input.agent);
9182
9560
  const repoPath = await resolveRepoRoot(input.repoPath);
9183
- if (!(0, import_node_fs21.existsSync)(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
9561
+ if (!(0, import_node_fs24.existsSync)(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
9184
9562
  if (!input.branches.length) throw new Error("At least one branch name required");
9185
9563
  const status = await detectGhStack(repoPath);
9186
9564
  if (!status.available) throw new Error(status.reason);
@@ -9247,7 +9625,7 @@ async function createPrStack(input, onSetupLine) {
9247
9625
  }
9248
9626
  }
9249
9627
  const claimed = new Set(threads.map((t) => t.worktreePath));
9250
- if (!claimed.has(bootstrap.worktreePath) && (0, import_node_fs21.existsSync)(bootstrap.worktreePath)) {
9628
+ if (!claimed.has(bootstrap.worktreePath) && (0, import_node_fs24.existsSync)(bootstrap.worktreePath)) {
9251
9629
  try {
9252
9630
  await removeWorktree(repoPath, bootstrap.worktreePath, {
9253
9631
  deleteBranch: bootstrap.branchName
@@ -9262,12 +9640,12 @@ async function createPrStack(input, onSetupLine) {
9262
9640
  init_worktree();
9263
9641
 
9264
9642
  // src/diff/diff.ts
9265
- var import_node_fs22 = require("fs");
9643
+ var import_node_fs25 = require("fs");
9266
9644
  var import_node_path20 = require("path");
9267
9645
  init_run();
9268
9646
  init_worktree();
9269
9647
  async function inspectGitWorktree(worktreePath) {
9270
- if (!worktreePath || !(0, import_node_fs22.existsSync)(worktreePath)) return "missing_worktree";
9648
+ if (!worktreePath || !(0, import_node_fs25.existsSync)(worktreePath)) return "missing_worktree";
9271
9649
  const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
9272
9650
  reject: false
9273
9651
  });
@@ -9275,7 +9653,7 @@ async function inspectGitWorktree(worktreePath) {
9275
9653
  return "ok";
9276
9654
  }
9277
9655
  async function initializeGitRepository(worktreePath) {
9278
- if (!worktreePath || !(0, import_node_fs22.existsSync)(worktreePath)) {
9656
+ if (!worktreePath || !(0, import_node_fs25.existsSync)(worktreePath)) {
9279
9657
  throw new Error("Worktree not found");
9280
9658
  }
9281
9659
  const status = await inspectGitWorktree(worktreePath);
@@ -9713,7 +10091,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
9713
10091
  assertSafeRelativePath(relativePath);
9714
10092
  const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
9715
10093
  const abs = (0, import_node_path20.join)(worktreePath, relativePath);
9716
- const st = (0, import_node_fs22.statSync)(abs);
10094
+ const st = (0, import_node_fs25.statSync)(abs);
9717
10095
  if (!st.isFile()) {
9718
10096
  throw new Error(`Not a file: ${relativePath}`);
9719
10097
  }
@@ -9722,7 +10100,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
9722
10100
  `File too large to upload (${st.size} bytes; max ${maxBytes})`
9723
10101
  );
9724
10102
  }
9725
- const buf = (0, import_node_fs22.readFileSync)(abs);
10103
+ const buf = (0, import_node_fs25.readFileSync)(abs);
9726
10104
  return {
9727
10105
  path: relativePath,
9728
10106
  contentBase64: buf.toString("base64"),
@@ -9733,11 +10111,11 @@ function readWorktreeFile(worktreePath, relativePath, opts) {
9733
10111
  assertSafeRelativePath(relativePath);
9734
10112
  const maxBytes = opts?.maxBytes ?? 2e5;
9735
10113
  const abs = (0, import_node_path20.join)(worktreePath, relativePath);
9736
- const st = (0, import_node_fs22.statSync)(abs);
10114
+ const st = (0, import_node_fs25.statSync)(abs);
9737
10115
  if (!st.isFile()) {
9738
10116
  throw new Error(`Not a file: ${relativePath}`);
9739
10117
  }
9740
- const buf = (0, import_node_fs22.readFileSync)(abs);
10118
+ const buf = (0, import_node_fs25.readFileSync)(abs);
9741
10119
  if (isImageRelativePath(relativePath)) {
9742
10120
  const maxImageBytes = Math.max(maxBytes, 15e6);
9743
10121
  const truncated2 = buf.length > maxImageBytes;
@@ -9781,8 +10159,8 @@ function assertSafeRelativePath(relativePath) {
9781
10159
  function writeWorktreeFile(worktreePath, relativePath, content) {
9782
10160
  assertSafeRelativePath(relativePath);
9783
10161
  const abs = (0, import_node_path20.join)(worktreePath, relativePath);
9784
- (0, import_node_fs22.mkdirSync)((0, import_node_path20.dirname)(abs), { recursive: true });
9785
- (0, import_node_fs22.writeFileSync)(abs, content, "utf8");
10162
+ (0, import_node_fs25.mkdirSync)((0, import_node_path20.dirname)(abs), { recursive: true });
10163
+ (0, import_node_fs25.writeFileSync)(abs, content, "utf8");
9786
10164
  return { path: relativePath };
9787
10165
  }
9788
10166
  async function getDiffSummary(worktreePath, repoPath, opts) {
@@ -9961,7 +10339,7 @@ async function confirmLand(thread, opts) {
9961
10339
  }
9962
10340
 
9963
10341
  // src/skills/discover.ts
9964
- var import_node_fs23 = require("fs");
10342
+ var import_node_fs26 = require("fs");
9965
10343
  var import_node_os9 = require("os");
9966
10344
  var import_node_path21 = require("path");
9967
10345
  function toCommand(name) {
@@ -9995,7 +10373,7 @@ function parseFrontmatter(content) {
9995
10373
  }
9996
10374
  function readSkill(skillMd, source) {
9997
10375
  try {
9998
- const content = (0, import_node_fs23.readFileSync)(skillMd, "utf8");
10376
+ const content = (0, import_node_fs26.readFileSync)(skillMd, "utf8");
9999
10377
  const { name: fmName, description } = parseFrontmatter(content);
10000
10378
  const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
10001
10379
  const name = fmName || dirName;
@@ -10014,19 +10392,19 @@ function readSkill(skillMd, source) {
10014
10392
  }
10015
10393
  }
10016
10394
  function scanSkillsDir(dir, source, out) {
10017
- if (!(0, import_node_fs23.existsSync)(dir)) return;
10395
+ if (!(0, import_node_fs26.existsSync)(dir)) return;
10018
10396
  let entries;
10019
10397
  try {
10020
- entries = (0, import_node_fs23.readdirSync)(dir);
10398
+ entries = (0, import_node_fs26.readdirSync)(dir);
10021
10399
  } catch {
10022
10400
  return;
10023
10401
  }
10024
10402
  for (const entry of entries) {
10025
10403
  if (entry.startsWith(".")) continue;
10026
10404
  const skillMd = (0, import_node_path21.join)(dir, entry, "SKILL.md");
10027
- if (!(0, import_node_fs23.existsSync)(skillMd)) continue;
10405
+ if (!(0, import_node_fs26.existsSync)(skillMd)) continue;
10028
10406
  try {
10029
- if (!(0, import_node_fs23.statSync)(skillMd).isFile()) continue;
10407
+ if (!(0, import_node_fs26.statSync)(skillMd).isFile()) continue;
10030
10408
  } catch {
10031
10409
  continue;
10032
10410
  }
@@ -10035,12 +10413,12 @@ function scanSkillsDir(dir, source, out) {
10035
10413
  }
10036
10414
  }
10037
10415
  function scanClaudePluginSkills(pluginsRoot, out) {
10038
- if (!(0, import_node_fs23.existsSync)(pluginsRoot)) return;
10416
+ if (!(0, import_node_fs26.existsSync)(pluginsRoot)) return;
10039
10417
  const walk = (dir, depth, lookingForSkillsDir) => {
10040
10418
  if (depth > 7) return;
10041
10419
  let entries;
10042
10420
  try {
10043
- entries = (0, import_node_fs23.readdirSync)(dir);
10421
+ entries = (0, import_node_fs26.readdirSync)(dir);
10044
10422
  } catch {
10045
10423
  return;
10046
10424
  }
@@ -10052,7 +10430,7 @@ function scanClaudePluginSkills(pluginsRoot, out) {
10052
10430
  if (entry === "node_modules" || entry === ".git") continue;
10053
10431
  const full = (0, import_node_path21.join)(dir, entry);
10054
10432
  try {
10055
- if (!(0, import_node_fs23.statSync)(full).isDirectory()) continue;
10433
+ if (!(0, import_node_fs26.statSync)(full).isDirectory()) continue;
10056
10434
  } catch {
10057
10435
  continue;
10058
10436
  }
@@ -10092,7 +10470,7 @@ function discoverSkills(worktreePath) {
10092
10470
  return [...byCommand.values()].sort((a, b) => a.command.localeCompare(b.command));
10093
10471
  }
10094
10472
  function readSkillBody(skillPath, maxChars = 12e3) {
10095
- const raw = (0, import_node_fs23.readFileSync)(skillPath, "utf8");
10473
+ const raw = (0, import_node_fs26.readFileSync)(skillPath, "utf8");
10096
10474
  if (raw.startsWith("---")) {
10097
10475
  const end = raw.indexOf("\n---", 3);
10098
10476
  if (end >= 0) {
@@ -10185,7 +10563,7 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
10185
10563
  }
10186
10564
 
10187
10565
  // src/composer/stage-files.ts
10188
- var import_node_fs24 = require("fs");
10566
+ var import_node_fs27 = require("fs");
10189
10567
  var import_node_path22 = require("path");
10190
10568
  var import_node_crypto5 = require("crypto");
10191
10569
  init_workspace_scratch();
@@ -10223,21 +10601,21 @@ function imageMimeType(filePath) {
10223
10601
  }
10224
10602
  function ensureAttachmentsDir(worktreePath) {
10225
10603
  const dir = (0, import_node_path22.join)(worktreePath, ATTACHMENTS_DIR);
10226
- (0, import_node_fs24.mkdirSync)(dir, { recursive: true });
10604
+ (0, import_node_fs27.mkdirSync)(dir, { recursive: true });
10227
10605
  const gi = (0, import_node_path22.join)(dir, ".gitignore");
10228
- if (!(0, import_node_fs24.existsSync)(gi)) {
10229
- (0, import_node_fs24.writeFileSync)(gi, attachmentsGitignoreBody(), "utf8");
10606
+ if (!(0, import_node_fs27.existsSync)(gi)) {
10607
+ (0, import_node_fs27.writeFileSync)(gi, attachmentsGitignoreBody(), "utf8");
10230
10608
  }
10231
10609
  return dir;
10232
10610
  }
10233
10611
  function uniqueAttachmentName(dir, originalName) {
10234
10612
  const safe = originalName.replace(/[/\\]/g, "_") || "file";
10235
- if (!(0, import_node_fs24.existsSync)((0, import_node_path22.join)(dir, safe))) return safe;
10613
+ if (!(0, import_node_fs27.existsSync)((0, import_node_path22.join)(dir, safe))) return safe;
10236
10614
  const ext = (0, import_node_path22.extname)(safe);
10237
10615
  const stem = ext ? safe.slice(0, -ext.length) : safe;
10238
10616
  for (let i = 1; i < 1e4; i++) {
10239
10617
  const candidate = `${stem}-${i}${ext}`;
10240
- if (!(0, import_node_fs24.existsSync)((0, import_node_path22.join)(dir, candidate))) return candidate;
10618
+ if (!(0, import_node_fs27.existsSync)((0, import_node_path22.join)(dir, candidate))) return candidate;
10241
10619
  }
10242
10620
  return `${stem}-${(0, import_node_crypto5.randomUUID)()}${ext}`;
10243
10621
  }
@@ -10295,13 +10673,13 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
10295
10673
  for (const abs of absolutePaths) {
10296
10674
  const originalName = (0, import_node_path22.basename)(abs);
10297
10675
  try {
10298
- const st = (0, import_node_fs24.statSync)(abs);
10676
+ const st = (0, import_node_fs27.statSync)(abs);
10299
10677
  if (!st.isFile()) continue;
10300
10678
  const name = uniqueAttachmentName(dir, originalName);
10301
10679
  const destAbs = (0, import_node_path22.join)(dir, name);
10302
- (0, import_node_fs24.copyFileSync)(abs, destAbs);
10680
+ (0, import_node_fs27.copyFileSync)(abs, destAbs);
10303
10681
  const rel = `${ATTACHMENTS_DIR}/${name}`;
10304
- const buf = (0, import_node_fs24.readFileSync)(destAbs);
10682
+ const buf = (0, import_node_fs27.readFileSync)(destAbs);
10305
10683
  out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
10306
10684
  } catch (err) {
10307
10685
  out.push({
@@ -10324,7 +10702,7 @@ function stageBuffersAsAttachments(worktreePath, buffers) {
10324
10702
  const buf = Buffer.from(item.dataBase64, "base64");
10325
10703
  const name = uniqueAttachmentName(dir, originalName);
10326
10704
  const destAbs = (0, import_node_path22.join)(dir, name);
10327
- (0, import_node_fs24.writeFileSync)(destAbs, buf);
10705
+ (0, import_node_fs27.writeFileSync)(destAbs, buf);
10328
10706
  const rel = `${ATTACHMENTS_DIR}/${name}`;
10329
10707
  out.push(attachmentFromBuffer(name, buf, { path: rel }));
10330
10708
  } catch (err) {
@@ -10353,9 +10731,9 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
10353
10731
  const name = (0, import_node_path22.basename)(rel);
10354
10732
  try {
10355
10733
  const abs = (0, import_node_path22.join)(worktreePath, rel);
10356
- const st = (0, import_node_fs24.statSync)(abs);
10734
+ const st = (0, import_node_fs27.statSync)(abs);
10357
10735
  if (!st.isFile()) continue;
10358
- const buf = (0, import_node_fs24.readFileSync)(abs);
10736
+ const buf = (0, import_node_fs27.readFileSync)(abs);
10359
10737
  out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
10360
10738
  } catch (err) {
10361
10739
  out.push({
@@ -10370,7 +10748,7 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
10370
10748
  }
10371
10749
 
10372
10750
  // src/agents/instructions.ts
10373
- var import_node_fs25 = require("fs");
10751
+ var import_node_fs28 = require("fs");
10374
10752
  var import_node_path23 = require("path");
10375
10753
  init_worktree_labels();
10376
10754
  function normPath2(p) {
@@ -10470,6 +10848,9 @@ function formatWorktreeDirective(thread, opts) {
10470
10848
  );
10471
10849
  return lines.join("\n");
10472
10850
  }
10851
+ function formatWorktreeReminder() {
10852
+ 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.";
10853
+ }
10473
10854
  function formatArtifactDirective() {
10474
10855
  return [
10475
10856
  "Sideboard side column (desktop UI):",
@@ -10490,60 +10871,6 @@ function formatArtifactDirective() {
10490
10871
  "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."
10491
10872
  ].join("\n");
10492
10873
  }
10493
- var FILES_BY_AGENT = {
10494
- claude: [
10495
- "CLAUDE.md",
10496
- ".claude/CLAUDE.md",
10497
- "CLAUDE.local.md",
10498
- "AGENTS.md",
10499
- "AGENT.md"
10500
- ],
10501
- codex: ["AGENTS.md", "AGENT.md", ".codex/AGENTS.md", "CLAUDE.md"],
10502
- opencode: ["AGENTS.md", "AGENT.md", "OPENCODE.md", "CLAUDE.md"],
10503
- brightsy: ["AGENTS.md", "AGENT.md", "CLAUDE.md"],
10504
- cursor: ["AGENTS.md", "AGENT.md", ".cursor/rules", "CLAUDE.md"]
10505
- };
10506
- var MAX_CHARS_PER_FILE = 48e3;
10507
- function loadAgentInstructions(worktreePath, agent) {
10508
- const candidates = FILES_BY_AGENT[agent] ?? FILES_BY_AGENT.claude;
10509
- const seen = /* @__PURE__ */ new Set();
10510
- const out = [];
10511
- for (const rel of candidates) {
10512
- if (seen.has(rel)) continue;
10513
- const abs = (0, import_node_path23.join)(worktreePath, rel);
10514
- if (!(0, import_node_fs25.existsSync)(abs)) continue;
10515
- try {
10516
- if (!(0, import_node_fs25.statSync)(abs).isFile()) continue;
10517
- let content = (0, import_node_fs25.readFileSync)(abs, "utf8");
10518
- if (!content.trim()) continue;
10519
- if (content.length > MAX_CHARS_PER_FILE) {
10520
- content = `${content.slice(0, MAX_CHARS_PER_FILE)}
10521
-
10522
- \u2026(truncated)`;
10523
- }
10524
- seen.add(rel);
10525
- out.push({ relativePath: rel, content });
10526
- } catch {
10527
- }
10528
- }
10529
- return out;
10530
- }
10531
- function formatAgentInstructions(files) {
10532
- if (files.length === 0) return null;
10533
- const parts = [
10534
- "Project agent instructions (from the worktree \u2014 follow these):",
10535
- ""
10536
- ];
10537
- for (const f of files) {
10538
- parts.push(`## ${f.relativePath}`);
10539
- parts.push("");
10540
- parts.push(f.content.trim());
10541
- parts.push("");
10542
- parts.push("---");
10543
- parts.push("");
10544
- }
10545
- return parts.join("\n").trimEnd();
10546
- }
10547
10874
 
10548
10875
  // src/orchestrator/orchestrator.ts
10549
10876
  init_types();
@@ -10668,7 +10995,7 @@ var Orchestrator = class {
10668
10995
  }
10669
10996
  continue;
10670
10997
  }
10671
- if (!(0, import_node_fs28.existsSync)(thread.worktreePath)) {
10998
+ if (!(0, import_node_fs31.existsSync)(thread.worktreePath)) {
10672
10999
  setStatus(thread.id, "broken", "Worktree missing on disk");
10673
11000
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
10674
11001
  continue;
@@ -11085,13 +11412,14 @@ var Orchestrator = class {
11085
11412
  "Files column: MCP present_files (brightsy account storage or memory).",
11086
11413
  "Do not say artifacts/CMS UI are unavailable."
11087
11414
  ].join(" ") : null;
11415
+ const worktreeReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatWorktreeReminder() : null;
11088
11416
  const agentPrompt = [
11089
11417
  thread.planMode ? PLAN_MODE_INSTRUCTION : null,
11090
11418
  orchestrationReminder,
11419
+ worktreeReminder,
11091
11420
  artifactReminder,
11092
11421
  expandedPrompt
11093
11422
  ].filter(Boolean).join("\n\n");
11094
- const instructionFiles = loadAgentInstructions(thread.worktreePath, thread.agent);
11095
11423
  if (thread.attachments.length > 0) {
11096
11424
  updateThread(threadId, { attachments: [] });
11097
11425
  }
@@ -11119,7 +11447,6 @@ var Orchestrator = class {
11119
11447
  const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled2() ? formatRenameBranchDirective(fresh, {
11120
11448
  customPrompt: settings?.prompts?.renameBranch
11121
11449
  }) : null;
11122
- const instructions = fresh.agent === "claude" || isBrightsy ? null : formatAgentInstructions(instructionFiles);
11123
11450
  let seed = null;
11124
11451
  if (!fresh.sessionId) {
11125
11452
  const prior = fresh.messages.slice(0, -1);
@@ -11140,12 +11467,12 @@ var Orchestrator = class {
11140
11467
  });
11141
11468
  }
11142
11469
  }
11143
- const cachedPrefix = [
11470
+ const cachedPrefix = fresh.sessionId ? "" : [
11144
11471
  coordinatorDirective,
11145
11472
  worktreeDirective,
11146
11473
  artifactDirective,
11147
11474
  renameBranchDirective,
11148
- ...fresh.agent === "claude" && fresh.sessionId ? [] : [instructions, seed]
11475
+ seed
11149
11476
  ].filter(Boolean).join("\n\n---\n\n");
11150
11477
  try {
11151
11478
  const stderrTail = [];
@@ -11232,15 +11559,11 @@ var Orchestrator = class {
11232
11559
  const retryThread = this.requireThread(threadId);
11233
11560
  const prior = retryThread.messages.slice(0, -1);
11234
11561
  const retrySeed = buildSessionSeed(prior);
11235
- const retryInstructions = retryThread.agent === "claude" ? null : formatAgentInstructions(
11236
- loadAgentInstructions(retryThread.worktreePath, retryThread.agent)
11237
- );
11238
11562
  const retryPrefix = [
11239
11563
  coordinatorDirective,
11240
11564
  worktreeDirective,
11241
11565
  artifactDirective,
11242
11566
  renameBranchDirective,
11243
- retryInstructions,
11244
11567
  retrySeed
11245
11568
  ].filter(Boolean).join("\n\n---\n\n");
11246
11569
  const retryHandle = await spawnAgentTurn(
@@ -11751,10 +12074,19 @@ var Orchestrator = class {
11751
12074
  this.assertNotGlobal(thread, "Merge PR");
11752
12075
  if (!selector) throw new Error("No pull request linked to this thread");
11753
12076
  const result = await mergePr(cwd, selector);
11754
- if (result.url && result.url !== thread.prUrl) {
11755
- updateThread(thread.id, { prUrl: result.url });
11756
- }
11757
- return result;
12077
+ const state = normalizePrState(result.state) || "MERGED";
12078
+ const metaLike = {
12079
+ number: 0,
12080
+ title: thread.prTitle ?? thread.title,
12081
+ url: result.url || thread.prUrl || "",
12082
+ state,
12083
+ isDraft: false,
12084
+ reviewDecision: null,
12085
+ baseRefName: "",
12086
+ headRefName: ""
12087
+ };
12088
+ await this.persistPrMetaAndMaybeArchive(thread, metaLike);
12089
+ return { url: metaLike.url, state };
11758
12090
  }
11759
12091
  /** Resolve PR selector and optionally persist `prUrl` when found. */
11760
12092
  async withPrSelector(threadRef) {
@@ -11776,19 +12108,55 @@ var Orchestrator = class {
11776
12108
  if (!selector) return null;
11777
12109
  const meta = await getPrMeta(cwd, selector);
11778
12110
  if (meta) {
11779
- const patch = {};
11780
- if (meta.url && meta.url !== thread.prUrl) patch.prUrl = meta.url;
11781
- if (meta.title && meta.title !== thread.prTitle) patch.prTitle = meta.title;
11782
- if (Object.keys(patch).length > 0) {
11783
- updateThread(thread.id, patch);
11784
- const latest = this.requireThread(thread.id);
11785
- if (!latest.userSetTitle && meta.title && latest.title !== meta.title) {
11786
- updateThread(thread.id, { title: meta.title });
11787
- }
11788
- }
12111
+ await this.persistPrMetaAndMaybeArchive(thread, meta);
11789
12112
  }
11790
12113
  return meta;
11791
12114
  }
12115
+ /**
12116
+ * Persist PR URL/title/state and Conductor-style auto-archive when the PR
12117
+ * first becomes MERGED.
12118
+ */
12119
+ async persistPrMetaAndMaybeArchive(thread, meta) {
12120
+ const prevState = normalizePrState(thread.prState);
12121
+ const nextState = normalizePrState(meta.state);
12122
+ const patch = {};
12123
+ if (meta.url && meta.url !== thread.prUrl) patch.prUrl = meta.url;
12124
+ if (meta.title && meta.title !== thread.prTitle) patch.prTitle = meta.title;
12125
+ if (nextState && nextState !== prevState) patch.prState = nextState;
12126
+ if (thread.skipAutoArchiveOnMerge && nextState && nextState !== "MERGED" && nextState !== "CLOSED") {
12127
+ patch.skipAutoArchiveOnMerge = false;
12128
+ }
12129
+ if (Object.keys(patch).length > 0) {
12130
+ updateThread(thread.id, patch);
12131
+ const latest2 = this.requireThread(thread.id);
12132
+ if (!latest2.userSetTitle && meta.title && latest2.title !== meta.title) {
12133
+ updateThread(thread.id, { title: meta.title });
12134
+ }
12135
+ }
12136
+ const { autoArchiveOnMergeEnabled: autoArchiveOnMergeEnabled2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
12137
+ const latest = this.requireThread(thread.id);
12138
+ if (!shouldAutoArchiveOnPrMerge({
12139
+ previousPrState: prevState || null,
12140
+ nextPrState: nextState,
12141
+ threadStatus: latest.status,
12142
+ skipAutoArchiveOnMerge: latest.skipAutoArchiveOnMerge,
12143
+ autoArchiveEnabled: autoArchiveOnMergeEnabled2(),
12144
+ isGlobal: isGlobalThread(latest)
12145
+ })) {
12146
+ return;
12147
+ }
12148
+ const siblings = threadsSharingWorktree(latest.worktreePath);
12149
+ for (const t of siblings) {
12150
+ const sibPatch = { prState: "MERGED" };
12151
+ if (meta.url && meta.url !== t.prUrl) sibPatch.prUrl = meta.url;
12152
+ if (meta.title && meta.title !== t.prTitle) sibPatch.prTitle = meta.title;
12153
+ if (Object.keys(sibPatch).length > 0) updateThread(t.id, sibPatch);
12154
+ }
12155
+ for (const t of siblings) {
12156
+ if (this.requireThread(t.id).status === "archived") continue;
12157
+ await this.archive(t.id);
12158
+ }
12159
+ }
11792
12160
  async getPrStack(threadRef) {
11793
12161
  const thread = this.requireThread(threadRef);
11794
12162
  if (!thread.worktreePath?.trim()) return null;
@@ -11966,7 +12334,9 @@ var Orchestrator = class {
11966
12334
  const thread = this.requireThread(threadRef);
11967
12335
  this.stop(thread.id);
11968
12336
  if (isGlobalThread(thread)) {
11969
- return setStatus(thread.id, "archived");
12337
+ const archived2 = setStatus(thread.id, "archived");
12338
+ this.emit({ type: "status_changed", threadId: archived2.id, status: "archived" });
12339
+ return archived2;
11970
12340
  }
11971
12341
  const siblings = threadsSharingWorktree(thread.worktreePath).filter((t) => t.id !== thread.id);
11972
12342
  if (siblings.length === 0) {
@@ -11984,6 +12354,7 @@ var Orchestrator = class {
11984
12354
  await removeWorktree(thread.repoPath, thread.worktreePath);
11985
12355
  }
11986
12356
  const archived = setStatus(thread.id, "archived");
12357
+ this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
11987
12358
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
11988
12359
  try {
11989
12360
  const { ensureWorkspace: ensureWorkspace2 } = await Promise.resolve().then(() => (init_workspaces(), workspaces_exports));
@@ -12023,9 +12394,11 @@ var Orchestrator = class {
12023
12394
  if (isGlobalThread(thread)) {
12024
12395
  const { globalAgentCwd: globalAgentCwd2 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
12025
12396
  updateThread(thread.id, { worktreePath: globalAgentCwd2() });
12026
- return setStatus(thread.id, "idle");
12397
+ const restored2 = setStatus(thread.id, "idle");
12398
+ this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
12399
+ return restored2;
12027
12400
  }
12028
- if (!(0, import_node_fs28.existsSync)(thread.worktreePath)) {
12401
+ if (!(0, import_node_fs31.existsSync)(thread.worktreePath)) {
12029
12402
  const { createThreadWorktree: createThreadWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
12030
12403
  const { execa: execa7 } = await import("execa");
12031
12404
  const slug = thread.worktreePath.split("/").pop();
@@ -12036,7 +12409,31 @@ var Orchestrator = class {
12036
12409
  void createThreadWorktree2;
12037
12410
  void slug;
12038
12411
  }
12039
- return setStatus(thread.id, "idle");
12412
+ const restorePatch = {};
12413
+ let alreadyMerged = normalizePrState(thread.prState) === "MERGED";
12414
+ if (!alreadyMerged && thread.prUrl?.trim() && thread.worktreePath?.trim()) {
12415
+ try {
12416
+ const selector = resolvePrSelector(thread);
12417
+ if (selector) {
12418
+ const meta = await getPrMeta(thread.worktreePath, selector);
12419
+ if (meta) {
12420
+ const state = normalizePrState(meta.state);
12421
+ if (meta.url && meta.url !== thread.prUrl) restorePatch.prUrl = meta.url;
12422
+ if (meta.title && meta.title !== thread.prTitle) restorePatch.prTitle = meta.title;
12423
+ if (state) restorePatch.prState = state;
12424
+ if (state === "MERGED") alreadyMerged = true;
12425
+ }
12426
+ }
12427
+ } catch {
12428
+ }
12429
+ }
12430
+ if (alreadyMerged) restorePatch.skipAutoArchiveOnMerge = true;
12431
+ if (Object.keys(restorePatch).length > 0) {
12432
+ updateThread(thread.id, restorePatch);
12433
+ }
12434
+ const restored = setStatus(thread.id, "idle");
12435
+ this.emit({ type: "status_changed", threadId: restored.id, status: restored.status });
12436
+ return restored;
12040
12437
  }
12041
12438
  async attachCommand(threadRef) {
12042
12439
  const thread = this.requireThread(threadRef);
@@ -12210,6 +12607,7 @@ function mcpArchiveBlockedReason(thread) {
12210
12607
  }
12211
12608
 
12212
12609
  // src/mcp/server.ts
12610
+ init_profile();
12213
12611
  var MAX_ORCH_THREADS = 5;
12214
12612
  var CREATE_THREAD_TIMEOUT_MS = 9e4;
12215
12613
  function withTimeout(promise, ms, label) {
@@ -12323,14 +12721,13 @@ async function startMcpServer() {
12323
12721
  ),
12324
12722
  artifact_id: import_zod.z.string().optional().describe("Stable id when updating the same artifact across turns")
12325
12723
  },
12326
- async ({ title, type, content, artifact_id }) => {
12724
+ async ({ title, type, artifact_id }) => {
12327
12725
  const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
12328
12726
  const payload = {
12329
12727
  ok: true,
12330
12728
  artifact_id: id,
12331
12729
  title,
12332
12730
  type,
12333
- content,
12334
12731
  message: "Artifact accepted. Sideboard desktop opens it in the side column beside chat."
12335
12732
  };
12336
12733
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
@@ -12383,7 +12780,6 @@ async function startMcpServer() {
12383
12780
  ok: true,
12384
12781
  path,
12385
12782
  title: title?.trim() || "Plan",
12386
- content,
12387
12783
  message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
12388
12784
  };
12389
12785
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
@@ -12413,9 +12809,6 @@ async function startMcpServer() {
12413
12809
  datasource: args.datasource ?? (args.resource ? "inline" : "brightsy"),
12414
12810
  resource_id: args.resource_id,
12415
12811
  record_id: args.record_id,
12416
- resource: args.resource,
12417
- record: args.record,
12418
- records: args.records,
12419
12812
  message: "Schema pane accepted. Sideboard desktop opens the CMS column beside chat."
12420
12813
  };
12421
12814
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
@@ -12443,739 +12836,741 @@ async function startMcpServer() {
12443
12836
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
12444
12837
  }
12445
12838
  );
12446
- const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
12447
- const accountDefaults = resolveThreadDefaults2();
12448
- const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
12449
- server.tool(
12450
- "create_thread",
12451
- `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.`,
12452
- {
12453
- sourceType: import_zod.z.enum(["branch", "pr", "ticket"]),
12454
- sourceRef: import_zod.z.string(),
12455
- agent: import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
12456
- model: import_zod.z.string().nullable().optional().describe(
12457
- `Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
12458
- ),
12459
- repoPath: import_zod.z.string(),
12460
- title: import_zod.z.string().optional(),
12461
- parentThreadId: import_zod.z.string().optional().describe(
12462
- "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
12463
- )
12464
- },
12465
- async (args) => {
12466
- const envParentId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || "";
12467
- let parentId = args.parentThreadId?.trim() || "";
12468
- let parent = parentId ? orch.getThread(parentId) : null;
12469
- let parentCorrectedFrom;
12470
- if (envParentId) {
12471
- const envParent = orch.getThread(envParentId);
12472
- if (envParent) {
12473
- if (parentId && parentId !== envParentId) parentCorrectedFrom = parentId;
12474
- else if (!parentId) parentCorrectedFrom = void 0;
12475
- parentId = envParentId;
12476
- parent = envParent;
12839
+ if (sideboardMcpProfile() !== "worktree") {
12840
+ const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
12841
+ const accountDefaults = resolveThreadDefaults2();
12842
+ const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
12843
+ server.tool(
12844
+ "create_thread",
12845
+ `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.`,
12846
+ {
12847
+ sourceType: import_zod.z.enum(["branch", "pr", "ticket"]),
12848
+ sourceRef: import_zod.z.string(),
12849
+ agent: import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
12850
+ model: import_zod.z.string().nullable().optional().describe(
12851
+ `Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
12852
+ ),
12853
+ repoPath: import_zod.z.string(),
12854
+ title: import_zod.z.string().optional(),
12855
+ parentThreadId: import_zod.z.string().optional().describe(
12856
+ "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
12857
+ )
12858
+ },
12859
+ async (args) => {
12860
+ const envParentId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || "";
12861
+ let parentId = args.parentThreadId?.trim() || "";
12862
+ let parent = parentId ? orch.getThread(parentId) : null;
12863
+ let parentCorrectedFrom;
12864
+ if (envParentId) {
12865
+ const envParent = orch.getThread(envParentId);
12866
+ if (envParent) {
12867
+ if (parentId && parentId !== envParentId) parentCorrectedFrom = parentId;
12868
+ else if (!parentId) parentCorrectedFrom = void 0;
12869
+ parentId = envParentId;
12870
+ parent = envParent;
12871
+ }
12477
12872
  }
12478
- }
12479
- if (parentId && !parent) {
12480
- parentCorrectedFrom = parentId;
12481
- parentId = "";
12482
- parent = null;
12483
- }
12484
- if (parentId) {
12485
- const children = orch.getThreads(false).filter((t) => t.parentThreadId === parentId);
12486
- if (children.length >= MAX_ORCH_THREADS) {
12873
+ if (parentId && !parent) {
12874
+ parentCorrectedFrom = parentId;
12875
+ parentId = "";
12876
+ parent = null;
12877
+ }
12878
+ if (parentId) {
12879
+ const children = orch.getThreads(false).filter((t) => t.parentThreadId === parentId);
12880
+ if (children.length >= MAX_ORCH_THREADS) {
12881
+ return {
12882
+ content: [
12883
+ {
12884
+ type: "text",
12885
+ text: `Thread-creation cap (${MAX_ORCH_THREADS}) reached for this orchestration session`
12886
+ }
12887
+ ],
12888
+ isError: true
12889
+ };
12890
+ }
12891
+ }
12892
+ let agentArg = args.agent;
12893
+ let agentCoercedFrom;
12894
+ const resolvedProbe = resolveNewThreadOptions2({ agent: agentArg }).agent;
12895
+ if (resolvedProbe === "codex") {
12896
+ agentCoercedFrom = agentArg ?? "codex";
12897
+ const accountAgent = resolveNewThreadOptions2({}).agent;
12898
+ agentArg = accountAgent !== "codex" ? accountAgent : "cursor";
12899
+ }
12900
+ const opts = resolveNewThreadOptions2({
12901
+ agent: agentArg,
12902
+ model: args.model
12903
+ });
12904
+ try {
12905
+ const thread = await withTimeout(
12906
+ orch.createThread({
12907
+ sourceType: args.sourceType,
12908
+ sourceRef: args.sourceRef,
12909
+ agent: opts.agent,
12910
+ model: opts.model,
12911
+ effort: opts.effort,
12912
+ fast: opts.fast,
12913
+ repoPath: args.repoPath,
12914
+ title: args.title,
12915
+ parentThreadId: parentId || null
12916
+ }),
12917
+ CREATE_THREAD_TIMEOUT_MS,
12918
+ "create_thread"
12919
+ );
12920
+ const parentNote = parentCorrectedFrom ? parentId ? `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; nested under ${parentId}` : `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; created without parent` : void 0;
12487
12921
  return {
12488
12922
  content: [
12489
12923
  {
12490
12924
  type: "text",
12491
- text: `Thread-creation cap (${MAX_ORCH_THREADS}) reached for this orchestration session`
12925
+ text: JSON.stringify({
12926
+ id: thread.id,
12927
+ title: thread.title,
12928
+ branchName: thread.branchName,
12929
+ worktreePath: thread.worktreePath,
12930
+ agent: thread.agent,
12931
+ model: thread.model,
12932
+ status: thread.status,
12933
+ link: `sideboard://thread/${thread.id}`,
12934
+ parentThreadId: thread.parentThreadId,
12935
+ ...agentCoercedFrom ? {
12936
+ agentCoercedFrom,
12937
+ note: `Avoid nested Codex under a Codex orchestrator \u2014 used Account default agent=${thread.agent}`
12938
+ } : {},
12939
+ ...parentCorrectedFrom ? { parentCorrectedFrom, parentNote } : {}
12940
+ })
12941
+ }
12942
+ ]
12943
+ };
12944
+ } catch (err) {
12945
+ const message = err instanceof Error ? err.message : String(err);
12946
+ return {
12947
+ content: [
12948
+ {
12949
+ type: "text",
12950
+ text: `create_thread failed: ${message}`
12492
12951
  }
12493
12952
  ],
12494
12953
  isError: true
12495
12954
  };
12496
12955
  }
12497
12956
  }
12498
- let agentArg = args.agent;
12499
- let agentCoercedFrom;
12500
- const resolvedProbe = resolveNewThreadOptions2({ agent: agentArg }).agent;
12501
- if (resolvedProbe === "codex") {
12502
- agentCoercedFrom = agentArg ?? "codex";
12503
- const accountAgent = resolveNewThreadOptions2({}).agent;
12504
- agentArg = accountAgent !== "codex" ? accountAgent : "cursor";
12505
- }
12506
- const opts = resolveNewThreadOptions2({
12507
- agent: agentArg,
12508
- model: args.model
12509
- });
12510
- try {
12511
- const thread = await withTimeout(
12512
- orch.createThread({
12513
- sourceType: args.sourceType,
12514
- sourceRef: args.sourceRef,
12515
- agent: opts.agent,
12516
- model: opts.model,
12517
- effort: opts.effort,
12518
- fast: opts.fast,
12519
- repoPath: args.repoPath,
12520
- title: args.title,
12521
- parentThreadId: parentId || null
12522
- }),
12523
- CREATE_THREAD_TIMEOUT_MS,
12524
- "create_thread"
12525
- );
12526
- const parentNote = parentCorrectedFrom ? parentId ? `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; nested under ${parentId}` : `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; created without parent` : void 0;
12957
+ );
12958
+ server.tool(
12959
+ "send_to_thread",
12960
+ "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.",
12961
+ {
12962
+ ref: import_zod.z.string(),
12963
+ prompt: import_zod.z.string(),
12964
+ force_stop: import_zod.z.boolean().optional()
12965
+ },
12966
+ async ({ ref, prompt, force_stop }) => {
12967
+ if (force_stop) {
12968
+ const existing = orch.getThread(ref);
12969
+ if (existing) {
12970
+ orch.stop(ref, { clearQueue: true });
12971
+ }
12972
+ }
12973
+ const thread = await orch.send(ref, prompt);
12527
12974
  return {
12528
12975
  content: [
12529
12976
  {
12530
12977
  type: "text",
12531
12978
  text: JSON.stringify({
12532
12979
  id: thread.id,
12533
- title: thread.title,
12534
- branchName: thread.branchName,
12535
- worktreePath: thread.worktreePath,
12536
- agent: thread.agent,
12537
- model: thread.model,
12538
12980
  status: thread.status,
12539
- link: `sideboard://thread/${thread.id}`,
12540
- parentThreadId: thread.parentThreadId,
12541
- ...agentCoercedFrom ? {
12542
- agentCoercedFrom,
12543
- note: `Avoid nested Codex under a Codex orchestrator \u2014 used Account default agent=${thread.agent}`
12544
- } : {},
12545
- ...parentCorrectedFrom ? { parentCorrectedFrom, parentNote } : {}
12981
+ queueLength: thread.queue.length,
12982
+ forceStopped: Boolean(force_stop)
12546
12983
  })
12547
12984
  }
12548
12985
  ]
12549
12986
  };
12550
- } catch (err) {
12551
- const message = err instanceof Error ? err.message : String(err);
12987
+ }
12988
+ );
12989
+ server.tool(
12990
+ "wait_for_turn",
12991
+ "Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread to read the agent reply.",
12992
+ {
12993
+ ref: import_zod.z.string(),
12994
+ timeoutMs: import_zod.z.number().optional()
12995
+ },
12996
+ async ({ ref, timeoutMs }) => {
12997
+ const thread = await orch.waitForTurn(ref, timeoutMs ?? 6e5);
12998
+ const result = orch.getTurnResult(thread.id);
12552
12999
  return {
12553
13000
  content: [
12554
13001
  {
12555
13002
  type: "text",
12556
- text: `create_thread failed: ${message}`
13003
+ text: JSON.stringify({
13004
+ id: thread.id,
13005
+ status: result.status,
13006
+ text: result.text
13007
+ })
12557
13008
  }
12558
- ],
12559
- isError: true
13009
+ ]
12560
13010
  };
12561
13011
  }
12562
- }
12563
- );
12564
- server.tool(
12565
- "send_to_thread",
12566
- "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.",
12567
- {
12568
- ref: import_zod.z.string(),
12569
- prompt: import_zod.z.string(),
12570
- force_stop: import_zod.z.boolean().optional()
12571
- },
12572
- async ({ ref, prompt, force_stop }) => {
12573
- if (force_stop) {
12574
- const existing = orch.getThread(ref);
12575
- if (existing) {
12576
- orch.stop(ref, { clearQueue: true });
12577
- }
13012
+ );
13013
+ server.tool(
13014
+ "get_turn_result",
13015
+ "Final assistant message only (not full transcript)",
13016
+ { ref: import_zod.z.string() },
13017
+ async ({ ref }) => {
13018
+ const result = orch.getTurnResult(ref);
13019
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
12578
13020
  }
12579
- const thread = await orch.send(ref, prompt);
12580
- return {
12581
- content: [
12582
- {
12583
- type: "text",
12584
- text: JSON.stringify({
12585
- id: thread.id,
12586
- status: thread.status,
12587
- queueLength: thread.queue.length,
12588
- forceStopped: Boolean(force_stop)
12589
- })
12590
- }
12591
- ]
12592
- };
12593
- }
12594
- );
12595
- server.tool(
12596
- "wait_for_turn",
12597
- "Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread to read the agent reply.",
12598
- {
12599
- ref: import_zod.z.string(),
12600
- timeoutMs: import_zod.z.number().optional()
12601
- },
12602
- async ({ ref, timeoutMs }) => {
12603
- const thread = await orch.waitForTurn(ref, timeoutMs ?? 6e5);
12604
- const result = orch.getTurnResult(thread.id);
12605
- return {
12606
- content: [
12607
- {
12608
- type: "text",
12609
- text: JSON.stringify({
12610
- id: thread.id,
12611
- status: result.status,
12612
- text: result.text
12613
- })
12614
- }
12615
- ]
12616
- };
12617
- }
12618
- );
12619
- server.tool(
12620
- "get_turn_result",
12621
- "Final assistant message only (not full transcript)",
12622
- { ref: import_zod.z.string() },
12623
- async ({ ref }) => {
12624
- const result = orch.getTurnResult(ref);
12625
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
12626
- }
12627
- );
12628
- server.tool(
12629
- "stop_thread",
12630
- "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.",
12631
- {
12632
- ref: import_zod.z.string(),
12633
- force: import_zod.z.boolean().optional()
12634
- },
12635
- async ({ ref, force }) => {
12636
- const t = orch.getThread(ref);
12637
- if (!t) {
13021
+ );
13022
+ server.tool(
13023
+ "stop_thread",
13024
+ "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.",
13025
+ {
13026
+ ref: import_zod.z.string(),
13027
+ force: import_zod.z.boolean().optional()
13028
+ },
13029
+ async ({ ref, force }) => {
13030
+ const t = orch.getThread(ref);
13031
+ if (!t) {
13032
+ return {
13033
+ content: [{ type: "text", text: `Thread not found: ${ref}` }],
13034
+ isError: true
13035
+ };
13036
+ }
13037
+ const clearQueue = force !== false;
13038
+ const hadQueued = t.queue.length > 0;
13039
+ const stopped = orch.stop(ref, { clearQueue });
12638
13040
  return {
12639
- content: [{ type: "text", text: `Thread not found: ${ref}` }],
12640
- isError: true
13041
+ content: [
13042
+ {
13043
+ type: "text",
13044
+ text: JSON.stringify({
13045
+ id: stopped.id,
13046
+ status: stopped.status,
13047
+ clearedQueue: clearQueue && hadQueued
13048
+ })
13049
+ }
13050
+ ]
12641
13051
  };
12642
13052
  }
12643
- const clearQueue = force !== false;
12644
- const hadQueued = t.queue.length > 0;
12645
- const stopped = orch.stop(ref, { clearQueue });
12646
- return {
12647
- content: [
12648
- {
12649
- type: "text",
12650
- text: JSON.stringify({
12651
- id: stopped.id,
12652
- status: stopped.status,
12653
- clearedQueue: clearQueue && hadQueued
12654
- })
12655
- }
12656
- ]
12657
- };
12658
- }
12659
- );
12660
- server.tool(
12661
- "archive_thread",
12662
- "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.",
12663
- { ref: import_zod.z.string() },
12664
- async ({ ref }) => {
12665
- const t = orch.getThread(ref);
12666
- if (!t) {
13053
+ );
13054
+ server.tool(
13055
+ "archive_thread",
13056
+ "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.",
13057
+ { ref: import_zod.z.string() },
13058
+ async ({ ref }) => {
13059
+ const t = orch.getThread(ref);
13060
+ if (!t) {
13061
+ return {
13062
+ content: [{ type: "text", text: `Thread not found: ${ref}` }],
13063
+ isError: true
13064
+ };
13065
+ }
13066
+ const blocked = mcpArchiveBlockedReason(t);
13067
+ if (blocked) {
13068
+ return {
13069
+ content: [{ type: "text", text: blocked }],
13070
+ isError: true
13071
+ };
13072
+ }
13073
+ const archived = await orch.archive(ref);
12667
13074
  return {
12668
- content: [{ type: "text", text: `Thread not found: ${ref}` }],
12669
- isError: true
13075
+ content: [
13076
+ {
13077
+ type: "text",
13078
+ text: JSON.stringify({
13079
+ id: archived.id,
13080
+ status: archived.status
13081
+ })
13082
+ }
13083
+ ]
12670
13084
  };
12671
13085
  }
12672
- const blocked = mcpArchiveBlockedReason(t);
12673
- if (blocked) {
12674
- return {
12675
- content: [{ type: "text", text: blocked }],
12676
- isError: true
12677
- };
13086
+ );
13087
+ server.tool(
13088
+ "restore_thread",
13089
+ "Restore an archived thread (recreates worktree from branch when needed)",
13090
+ { ref: import_zod.z.string() },
13091
+ async ({ ref }) => {
13092
+ try {
13093
+ const restored = await orch.restore(ref);
13094
+ return {
13095
+ content: [
13096
+ {
13097
+ type: "text",
13098
+ text: JSON.stringify({
13099
+ id: restored.id,
13100
+ status: restored.status,
13101
+ worktreePath: restored.worktreePath
13102
+ })
13103
+ }
13104
+ ]
13105
+ };
13106
+ } catch (err) {
13107
+ const message = err instanceof Error ? err.message : String(err);
13108
+ return { content: [{ type: "text", text: message }], isError: true };
13109
+ }
12678
13110
  }
12679
- const archived = await orch.archive(ref);
12680
- return {
12681
- content: [
12682
- {
12683
- type: "text",
12684
- text: JSON.stringify({
12685
- id: archived.id,
12686
- status: archived.status
12687
- })
12688
- }
12689
- ]
12690
- };
12691
- }
12692
- );
12693
- server.tool(
12694
- "restore_thread",
12695
- "Restore an archived thread (recreates worktree from branch when needed)",
12696
- { ref: import_zod.z.string() },
12697
- async ({ ref }) => {
12698
- try {
12699
- const restored = await orch.restore(ref);
13111
+ );
13112
+ server.tool(
13113
+ "get_diff",
13114
+ "Compact diff summary (capped hunks, paginated)",
13115
+ {
13116
+ ref: import_zod.z.string(),
13117
+ maxFiles: import_zod.z.number().optional()
13118
+ },
13119
+ async ({ ref, maxFiles }) => {
13120
+ const summary = await orch.diffSummary(ref);
12700
13121
  return {
12701
13122
  content: [
12702
13123
  {
12703
13124
  type: "text",
12704
13125
  text: JSON.stringify({
12705
- id: restored.id,
12706
- status: restored.status,
12707
- worktreePath: restored.worktreePath
13126
+ ...summary,
13127
+ files: summary.files.slice(0, maxFiles ?? 10)
12708
13128
  })
12709
13129
  }
12710
13130
  ]
12711
13131
  };
12712
- } catch (err) {
12713
- const message = err instanceof Error ? err.message : String(err);
12714
- return { content: [{ type: "text", text: message }], isError: true };
12715
13132
  }
12716
- }
12717
- );
12718
- server.tool(
12719
- "get_diff",
12720
- "Compact diff summary (capped hunks, paginated)",
12721
- {
12722
- ref: import_zod.z.string(),
12723
- maxFiles: import_zod.z.number().optional()
12724
- },
12725
- async ({ ref, maxFiles }) => {
12726
- const summary = await orch.diffSummary(ref);
12727
- return {
12728
- content: [
12729
- {
12730
- type: "text",
12731
- text: JSON.stringify({
12732
- ...summary,
12733
- files: summary.files.slice(0, maxFiles ?? 10)
12734
- })
13133
+ );
13134
+ server.tool(
13135
+ "request_review",
13136
+ '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.',
13137
+ { ref: import_zod.z.string().describe("Worktree thread id/ref to review") },
13138
+ async ({ ref }) => {
13139
+ try {
13140
+ const tab = await orch.requestReview(ref);
13141
+ const from = orch.getThread(ref);
13142
+ return {
13143
+ content: [
13144
+ {
13145
+ type: "text",
13146
+ text: JSON.stringify({
13147
+ id: tab.id,
13148
+ title: tab.title,
13149
+ status: tab.status,
13150
+ fromThreadId: from?.id ?? ref,
13151
+ link: `sideboard://thread/${tab.id}`
13152
+ })
13153
+ }
13154
+ ]
13155
+ };
13156
+ } catch (err) {
13157
+ const message = err instanceof Error ? err.message : String(err);
13158
+ return { content: [{ type: "text", text: message }], isError: true };
13159
+ }
13160
+ }
13161
+ );
13162
+ const agentEnum = import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
13163
+ server.tool(
13164
+ "list_models",
13165
+ "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.",
13166
+ {
13167
+ agent: agentEnum.optional().describe("Limit to one agent; omit for all")
13168
+ },
13169
+ async ({ agent }) => {
13170
+ try {
13171
+ const catalogs = await listModelsForAgent(agent);
13172
+ return {
13173
+ content: [{ type: "text", text: JSON.stringify(catalogs, null, 2) }]
13174
+ };
13175
+ } catch (err) {
13176
+ const message = err instanceof Error ? err.message : String(err);
13177
+ return { content: [{ type: "text", text: message }], isError: true };
13178
+ }
13179
+ }
13180
+ );
13181
+ server.tool(
13182
+ "fork_worktree",
13183
+ "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.",
13184
+ {
13185
+ ref: import_zod.z.string().describe("Worktree thread id/ref to fork"),
13186
+ through_index: import_zod.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
13187
+ agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
13188
+ model: import_zod.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
13189
+ title: import_zod.z.string().optional()
13190
+ },
13191
+ async ({ ref, through_index, agent, model, title }) => {
13192
+ try {
13193
+ const source = orch.getThread(ref);
13194
+ if (source) await orch.reconcile(source.repoPath, { drainQueues: false });
13195
+ const thread = await orch.forkThreadWorktree({
13196
+ threadId: ref,
13197
+ throughIndex: through_index,
13198
+ agent,
13199
+ model,
13200
+ title
13201
+ });
13202
+ return {
13203
+ content: [
13204
+ {
13205
+ type: "text",
13206
+ text: JSON.stringify({
13207
+ id: thread.id,
13208
+ title: thread.title,
13209
+ status: thread.status,
13210
+ agent: thread.agent,
13211
+ model: thread.model,
13212
+ branchName: thread.branchName,
13213
+ worktreePath: thread.worktreePath,
13214
+ fromThreadId: source?.id ?? ref,
13215
+ link: `sideboard://thread/${thread.id}`
13216
+ })
13217
+ }
13218
+ ]
13219
+ };
13220
+ } catch (err) {
13221
+ const message = err instanceof Error ? err.message : String(err);
13222
+ return { content: [{ type: "text", text: message }], isError: true };
13223
+ }
13224
+ }
13225
+ );
13226
+ server.tool(
13227
+ "fork_chat",
13228
+ "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.",
13229
+ {
13230
+ ref: import_zod.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
13231
+ through_index: import_zod.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
13232
+ agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
13233
+ model: import_zod.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
13234
+ title: import_zod.z.string().optional()
13235
+ },
13236
+ async ({ ref, through_index, agent, model, title }) => {
13237
+ try {
13238
+ const source = orch.getThread(ref);
13239
+ if (!source) {
13240
+ return {
13241
+ content: [{ type: "text", text: `Thread not found: ${ref}` }],
13242
+ isError: true
13243
+ };
12735
13244
  }
12736
- ]
12737
- };
12738
- }
12739
- );
12740
- server.tool(
12741
- "request_review",
12742
- '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.',
12743
- { ref: import_zod.z.string().describe("Worktree thread id/ref to review") },
12744
- async ({ ref }) => {
12745
- try {
12746
- const tab = await orch.requestReview(ref);
12747
- const from = orch.getThread(ref);
13245
+ const tab = orch.forkChatTab({
13246
+ threadId: source.id,
13247
+ throughIndex: through_index,
13248
+ agent,
13249
+ model,
13250
+ title
13251
+ });
13252
+ return {
13253
+ content: [
13254
+ {
13255
+ type: "text",
13256
+ text: JSON.stringify({
13257
+ id: tab.id,
13258
+ title: tab.title,
13259
+ status: tab.status,
13260
+ agent: tab.agent,
13261
+ model: tab.model,
13262
+ sourceType: tab.sourceType,
13263
+ worktreePath: tab.worktreePath,
13264
+ fromThreadId: source.id,
13265
+ link: `sideboard://thread/${tab.id}`
13266
+ })
13267
+ }
13268
+ ]
13269
+ };
13270
+ } catch (err) {
13271
+ const message = err instanceof Error ? err.message : String(err);
13272
+ return { content: [{ type: "text", text: message }], isError: true };
13273
+ }
13274
+ }
13275
+ );
13276
+ server.tool(
13277
+ "run_dev_script",
13278
+ "Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
13279
+ {
13280
+ ref: import_zod.z.string(),
13281
+ name: import_zod.z.string().optional()
13282
+ },
13283
+ async ({ ref, name }) => {
13284
+ const result = await orch.startDev(ref, name);
12748
13285
  return {
12749
13286
  content: [
12750
13287
  {
12751
13288
  type: "text",
12752
13289
  text: JSON.stringify({
12753
- id: tab.id,
12754
- title: tab.title,
12755
- status: tab.status,
12756
- fromThreadId: from?.id ?? ref,
12757
- link: `sideboard://thread/${tab.id}`
13290
+ port: result.port,
13291
+ scriptName: result.scriptName,
13292
+ ports: result.ports,
13293
+ url: `http://localhost:${result.port}`
12758
13294
  })
12759
13295
  }
12760
13296
  ]
12761
13297
  };
12762
- } catch (err) {
12763
- const message = err instanceof Error ? err.message : String(err);
12764
- return { content: [{ type: "text", text: message }], isError: true };
12765
13298
  }
12766
- }
12767
- );
12768
- const agentEnum = import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
12769
- server.tool(
12770
- "list_models",
12771
- "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.",
12772
- {
12773
- agent: agentEnum.optional().describe("Limit to one agent; omit for all")
12774
- },
12775
- async ({ agent }) => {
12776
- try {
12777
- const catalogs = await listModelsForAgent(agent);
13299
+ );
13300
+ server.tool(
13301
+ "list_run_scripts",
13302
+ "List named run scripts available for a thread",
13303
+ { ref: import_zod.z.string() },
13304
+ async ({ ref }) => {
13305
+ const scripts = orch.listThreadRunScripts(ref);
13306
+ const active = orch.getActiveRuns(ref);
12778
13307
  return {
12779
- content: [{ type: "text", text: JSON.stringify(catalogs, null, 2) }]
13308
+ content: [
13309
+ {
13310
+ type: "text",
13311
+ text: JSON.stringify({ scripts, active }, null, 2)
13312
+ }
13313
+ ]
12780
13314
  };
12781
- } catch (err) {
12782
- const message = err instanceof Error ? err.message : String(err);
12783
- return { content: [{ type: "text", text: message }], isError: true };
12784
13315
  }
12785
- }
12786
- );
12787
- server.tool(
12788
- "fork_worktree",
12789
- "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.",
12790
- {
12791
- ref: import_zod.z.string().describe("Worktree thread id/ref to fork"),
12792
- through_index: import_zod.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
12793
- agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
12794
- model: import_zod.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
12795
- title: import_zod.z.string().optional()
12796
- },
12797
- async ({ ref, through_index, agent, model, title }) => {
12798
- try {
12799
- const source = orch.getThread(ref);
12800
- if (source) await orch.reconcile(source.repoPath, { drainQueues: false });
12801
- const thread = await orch.forkThreadWorktree({
12802
- threadId: ref,
12803
- throughIndex: through_index,
12804
- agent,
12805
- model,
12806
- title
13316
+ );
13317
+ server.tool(
13318
+ "stop_dev_script",
13319
+ "Stop a running script for a thread (all scripts if name omitted)",
13320
+ {
13321
+ ref: import_zod.z.string(),
13322
+ name: import_zod.z.string().optional()
13323
+ },
13324
+ async ({ ref, name }) => {
13325
+ orch.stopDev(ref, name);
13326
+ return { content: [{ type: "text", text: "ok" }] };
13327
+ }
13328
+ );
13329
+ server.tool(
13330
+ "run_setup",
13331
+ "Re-run workspace setup (Sideboard/Conductor settings or .cursor/worktrees.json)",
13332
+ { ref: import_zod.z.string() },
13333
+ async ({ ref }) => {
13334
+ const result = await orch.runSetup(ref);
13335
+ return {
13336
+ content: [{ type: "text", text: JSON.stringify(result) }]
13337
+ };
13338
+ }
13339
+ );
13340
+ server.tool(
13341
+ "add_workspace",
13342
+ "Register a git repo as a Sideboard workspace",
13343
+ { repoPath: import_zod.z.string() },
13344
+ async ({ repoPath }) => {
13345
+ const ws = await orch.addWorkspace(repoPath);
13346
+ return { content: [{ type: "text", text: JSON.stringify(ws) }] };
13347
+ }
13348
+ );
13349
+ server.tool(
13350
+ "remove_workspace",
13351
+ "Unregister a Sideboard workspace (does not archive threads)",
13352
+ { repoPath: import_zod.z.string() },
13353
+ async ({ repoPath }) => {
13354
+ orch.removeWorkspace(repoPath);
13355
+ return { content: [{ type: "text", text: "ok" }] };
13356
+ }
13357
+ );
13358
+ server.tool(
13359
+ "fanout",
13360
+ "Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
13361
+ {
13362
+ prompt: import_zod.z.string(),
13363
+ agents: import_zod.z.array(import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
13364
+ repoPath: import_zod.z.string(),
13365
+ sourceType: import_zod.z.enum(["branch", "pr", "ticket"]).optional(),
13366
+ sourceRef: import_zod.z.string().optional(),
13367
+ title: import_zod.z.string().optional()
13368
+ },
13369
+ async (args) => {
13370
+ const threads = await orch.bestOfN(args);
13371
+ return {
13372
+ content: [
13373
+ {
13374
+ type: "text",
13375
+ text: JSON.stringify(
13376
+ threads.map((t) => ({
13377
+ id: t.id,
13378
+ agent: t.agent,
13379
+ branchName: t.branchName,
13380
+ worktreePath: t.worktreePath
13381
+ })),
13382
+ null,
13383
+ 2
13384
+ )
13385
+ }
13386
+ ]
13387
+ };
13388
+ }
13389
+ );
13390
+ server.tool(
13391
+ "list_branches",
13392
+ "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).",
13393
+ {
13394
+ repoPath: import_zod.z.string(),
13395
+ unmergedOnly: import_zod.z.boolean().optional()
13396
+ },
13397
+ async ({ repoPath, unmergedOnly }) => {
13398
+ const root = await resolveRepoRoot(repoPath);
13399
+ const branches = await listBranches(root, {
13400
+ unmergedOnly: unmergedOnly !== false
12807
13401
  });
12808
13402
  return {
12809
13403
  content: [
12810
13404
  {
12811
13405
  type: "text",
12812
- text: JSON.stringify({
12813
- id: thread.id,
12814
- title: thread.title,
12815
- status: thread.status,
12816
- agent: thread.agent,
12817
- model: thread.model,
12818
- branchName: thread.branchName,
12819
- worktreePath: thread.worktreePath,
12820
- fromThreadId: source?.id ?? ref,
12821
- link: `sideboard://thread/${thread.id}`
12822
- })
13406
+ text: branches.map((b) => `${b.current ? "*" : " "} ${b.name}`).join("\n")
12823
13407
  }
12824
13408
  ]
12825
13409
  };
12826
- } catch (err) {
12827
- const message = err instanceof Error ? err.message : String(err);
12828
- return { content: [{ type: "text", text: message }], isError: true };
12829
13410
  }
12830
- }
12831
- );
12832
- server.tool(
12833
- "fork_chat",
12834
- "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.",
12835
- {
12836
- ref: import_zod.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
12837
- through_index: import_zod.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
12838
- agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
12839
- model: import_zod.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
12840
- title: import_zod.z.string().optional()
12841
- },
12842
- async ({ ref, through_index, agent, model, title }) => {
12843
- try {
12844
- const source = orch.getThread(ref);
12845
- if (!source) {
12846
- return {
12847
- content: [{ type: "text", text: `Thread not found: ${ref}` }],
12848
- isError: true
12849
- };
12850
- }
12851
- const tab = orch.forkChatTab({
12852
- threadId: source.id,
12853
- throughIndex: through_index,
12854
- agent,
12855
- model,
12856
- title
13411
+ );
13412
+ server.tool(
13413
+ "list_prs",
13414
+ "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.",
13415
+ { repoPath: import_zod.z.string() },
13416
+ async ({ repoPath }) => {
13417
+ const root = await resolveRepoRoot(repoPath);
13418
+ const prs = await listPrs(root);
13419
+ return {
13420
+ content: [
13421
+ {
13422
+ type: "text",
13423
+ text: prs.map(
13424
+ (p) => `#${p.number} ${p.title} (${p.headRefName})${p.isCrossRepository ? " [fork]" : ""}`
13425
+ ).join("\n")
13426
+ }
13427
+ ]
13428
+ };
13429
+ }
13430
+ );
13431
+ server.tool(
13432
+ "get_pr_stack",
13433
+ "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.",
13434
+ { ref: import_zod.z.string() },
13435
+ async ({ ref }) => {
13436
+ const stack = await orch.getPrStack(ref);
13437
+ return {
13438
+ content: [{ type: "text", text: JSON.stringify(stack, null, 2) }]
13439
+ };
13440
+ }
13441
+ );
13442
+ server.tool(
13443
+ "open_pr_stack_layers",
13444
+ "Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
13445
+ {
13446
+ ref: import_zod.z.string(),
13447
+ layer: import_zod.z.number().int().positive().optional()
13448
+ },
13449
+ async ({ ref, layer }) => {
13450
+ const result = await orch.openPrStackLayers(ref, { layer });
13451
+ return {
13452
+ content: [
13453
+ {
13454
+ type: "text",
13455
+ text: JSON.stringify(
13456
+ {
13457
+ stackNumber: result.stack.stackNumber,
13458
+ trunk: result.stack.trunk,
13459
+ threads: result.threads.map((t) => ({
13460
+ id: t.id,
13461
+ title: t.title,
13462
+ branchName: t.branchName,
13463
+ stackLayer: t.stackLayer,
13464
+ worktreePath: t.worktreePath,
13465
+ prUrl: t.prUrl,
13466
+ link: `sideboard://thread/${t.id}`
13467
+ }))
13468
+ },
13469
+ null,
13470
+ 2
13471
+ )
13472
+ }
13473
+ ]
13474
+ };
13475
+ }
13476
+ );
13477
+ server.tool(
13478
+ "add_stack_layer",
13479
+ "Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
13480
+ {
13481
+ ref: import_zod.z.string(),
13482
+ branchName: import_zod.z.string(),
13483
+ title: import_zod.z.string().optional()
13484
+ },
13485
+ async ({ ref, branchName, title }) => {
13486
+ const result = await orch.addStackLayer(ref, branchName, { title });
13487
+ return {
13488
+ content: [
13489
+ {
13490
+ type: "text",
13491
+ text: JSON.stringify(
13492
+ {
13493
+ id: result.thread.id,
13494
+ title: result.thread.title,
13495
+ branchName: result.thread.branchName,
13496
+ stackLayer: result.thread.stackLayer,
13497
+ worktreePath: result.thread.worktreePath,
13498
+ link: `sideboard://thread/${result.thread.id}`
13499
+ },
13500
+ null,
13501
+ 2
13502
+ )
13503
+ }
13504
+ ]
13505
+ };
13506
+ }
13507
+ );
13508
+ server.tool(
13509
+ "create_pr_stack",
13510
+ "Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
13511
+ {
13512
+ repoPath: import_zod.z.string(),
13513
+ branches: import_zod.z.array(import_zod.z.string()).min(1),
13514
+ agent: import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
13515
+ base: import_zod.z.string().optional(),
13516
+ title: import_zod.z.string().optional()
13517
+ },
13518
+ async (args) => {
13519
+ const result = await orch.createPrStack({
13520
+ repoPath: args.repoPath,
13521
+ branches: args.branches,
13522
+ agent: args.agent,
13523
+ base: args.base,
13524
+ title: args.title
12857
13525
  });
12858
13526
  return {
12859
13527
  content: [
12860
13528
  {
12861
13529
  type: "text",
12862
- text: JSON.stringify({
12863
- id: tab.id,
12864
- title: tab.title,
12865
- status: tab.status,
12866
- agent: tab.agent,
12867
- model: tab.model,
12868
- sourceType: tab.sourceType,
12869
- worktreePath: tab.worktreePath,
12870
- fromThreadId: source.id,
12871
- link: `sideboard://thread/${tab.id}`
12872
- })
13530
+ text: JSON.stringify(
13531
+ {
13532
+ stackNumber: result.stack.stackNumber,
13533
+ trunk: result.stack.trunk,
13534
+ threads: result.threads.map((t) => ({
13535
+ id: t.id,
13536
+ title: t.title,
13537
+ branchName: t.branchName,
13538
+ stackLayer: t.stackLayer,
13539
+ worktreePath: t.worktreePath,
13540
+ link: `sideboard://thread/${t.id}`
13541
+ }))
13542
+ },
13543
+ null,
13544
+ 2
13545
+ )
12873
13546
  }
12874
13547
  ]
12875
13548
  };
12876
- } catch (err) {
12877
- const message = err instanceof Error ? err.message : String(err);
12878
- return { content: [{ type: "text", text: message }], isError: true };
12879
13549
  }
12880
- }
12881
- );
12882
- server.tool(
12883
- "run_dev_script",
12884
- "Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
12885
- {
12886
- ref: import_zod.z.string(),
12887
- name: import_zod.z.string().optional()
12888
- },
12889
- async ({ ref, name }) => {
12890
- const result = await orch.startDev(ref, name);
12891
- return {
12892
- content: [
12893
- {
12894
- type: "text",
12895
- text: JSON.stringify({
12896
- port: result.port,
12897
- scriptName: result.scriptName,
12898
- ports: result.ports,
12899
- url: `http://localhost:${result.port}`
12900
- })
12901
- }
12902
- ]
12903
- };
12904
- }
12905
- );
12906
- server.tool(
12907
- "list_run_scripts",
12908
- "List named run scripts available for a thread",
12909
- { ref: import_zod.z.string() },
12910
- async ({ ref }) => {
12911
- const scripts = orch.listThreadRunScripts(ref);
12912
- const active = orch.getActiveRuns(ref);
12913
- return {
12914
- content: [
12915
- {
12916
- type: "text",
12917
- text: JSON.stringify({ scripts, active }, null, 2)
12918
- }
12919
- ]
12920
- };
12921
- }
12922
- );
12923
- server.tool(
12924
- "stop_dev_script",
12925
- "Stop a running script for a thread (all scripts if name omitted)",
12926
- {
12927
- ref: import_zod.z.string(),
12928
- name: import_zod.z.string().optional()
12929
- },
12930
- async ({ ref, name }) => {
12931
- orch.stopDev(ref, name);
12932
- return { content: [{ type: "text", text: "ok" }] };
12933
- }
12934
- );
12935
- server.tool(
12936
- "run_setup",
12937
- "Re-run workspace setup (Sideboard/Conductor settings or .cursor/worktrees.json)",
12938
- { ref: import_zod.z.string() },
12939
- async ({ ref }) => {
12940
- const result = await orch.runSetup(ref);
12941
- return {
12942
- content: [{ type: "text", text: JSON.stringify(result) }]
12943
- };
12944
- }
12945
- );
12946
- server.tool(
12947
- "add_workspace",
12948
- "Register a git repo as a Sideboard workspace",
12949
- { repoPath: import_zod.z.string() },
12950
- async ({ repoPath }) => {
12951
- const ws = await orch.addWorkspace(repoPath);
12952
- return { content: [{ type: "text", text: JSON.stringify(ws) }] };
12953
- }
12954
- );
12955
- server.tool(
12956
- "remove_workspace",
12957
- "Unregister a Sideboard workspace (does not archive threads)",
12958
- { repoPath: import_zod.z.string() },
12959
- async ({ repoPath }) => {
12960
- orch.removeWorkspace(repoPath);
12961
- return { content: [{ type: "text", text: "ok" }] };
12962
- }
12963
- );
12964
- server.tool(
12965
- "fanout",
12966
- "Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
12967
- {
12968
- prompt: import_zod.z.string(),
12969
- agents: import_zod.z.array(import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
12970
- repoPath: import_zod.z.string(),
12971
- sourceType: import_zod.z.enum(["branch", "pr", "ticket"]).optional(),
12972
- sourceRef: import_zod.z.string().optional(),
12973
- title: import_zod.z.string().optional()
12974
- },
12975
- async (args) => {
12976
- const threads = await orch.bestOfN(args);
12977
- return {
12978
- content: [
12979
- {
12980
- type: "text",
12981
- text: JSON.stringify(
12982
- threads.map((t) => ({
12983
- id: t.id,
12984
- agent: t.agent,
12985
- branchName: t.branchName,
12986
- worktreePath: t.worktreePath
12987
- })),
12988
- null,
12989
- 2
12990
- )
12991
- }
12992
- ]
12993
- };
12994
- }
12995
- );
12996
- server.tool(
12997
- "list_branches",
12998
- "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).",
12999
- {
13000
- repoPath: import_zod.z.string(),
13001
- unmergedOnly: import_zod.z.boolean().optional()
13002
- },
13003
- async ({ repoPath, unmergedOnly }) => {
13004
- const root = await resolveRepoRoot(repoPath);
13005
- const branches = await listBranches(root, {
13006
- unmergedOnly: unmergedOnly !== false
13007
- });
13008
- return {
13009
- content: [
13010
- {
13011
- type: "text",
13012
- text: branches.map((b) => `${b.current ? "*" : " "} ${b.name}`).join("\n")
13013
- }
13014
- ]
13015
- };
13016
- }
13017
- );
13018
- server.tool(
13019
- "list_prs",
13020
- "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.",
13021
- { repoPath: import_zod.z.string() },
13022
- async ({ repoPath }) => {
13023
- const root = await resolveRepoRoot(repoPath);
13024
- const prs = await listPrs(root);
13025
- return {
13026
- content: [
13027
- {
13028
- type: "text",
13029
- text: prs.map(
13030
- (p) => `#${p.number} ${p.title} (${p.headRefName})${p.isCrossRepository ? " [fork]" : ""}`
13031
- ).join("\n")
13032
- }
13033
- ]
13034
- };
13035
- }
13036
- );
13037
- server.tool(
13038
- "get_pr_stack",
13039
- "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.",
13040
- { ref: import_zod.z.string() },
13041
- async ({ ref }) => {
13042
- const stack = await orch.getPrStack(ref);
13043
- return {
13044
- content: [{ type: "text", text: JSON.stringify(stack, null, 2) }]
13045
- };
13046
- }
13047
- );
13048
- server.tool(
13049
- "open_pr_stack_layers",
13050
- "Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
13051
- {
13052
- ref: import_zod.z.string(),
13053
- layer: import_zod.z.number().int().positive().optional()
13054
- },
13055
- async ({ ref, layer }) => {
13056
- const result = await orch.openPrStackLayers(ref, { layer });
13057
- return {
13058
- content: [
13059
- {
13060
- type: "text",
13061
- text: JSON.stringify(
13062
- {
13063
- stackNumber: result.stack.stackNumber,
13064
- trunk: result.stack.trunk,
13065
- threads: result.threads.map((t) => ({
13066
- id: t.id,
13067
- title: t.title,
13068
- branchName: t.branchName,
13069
- stackLayer: t.stackLayer,
13070
- worktreePath: t.worktreePath,
13071
- prUrl: t.prUrl,
13072
- link: `sideboard://thread/${t.id}`
13073
- }))
13074
- },
13075
- null,
13076
- 2
13077
- )
13078
- }
13079
- ]
13080
- };
13081
- }
13082
- );
13083
- server.tool(
13084
- "add_stack_layer",
13085
- "Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
13086
- {
13087
- ref: import_zod.z.string(),
13088
- branchName: import_zod.z.string(),
13089
- title: import_zod.z.string().optional()
13090
- },
13091
- async ({ ref, branchName, title }) => {
13092
- const result = await orch.addStackLayer(ref, branchName, { title });
13093
- return {
13094
- content: [
13095
- {
13096
- type: "text",
13097
- text: JSON.stringify(
13098
- {
13099
- id: result.thread.id,
13100
- title: result.thread.title,
13101
- branchName: result.thread.branchName,
13102
- stackLayer: result.thread.stackLayer,
13103
- worktreePath: result.thread.worktreePath,
13104
- link: `sideboard://thread/${result.thread.id}`
13105
- },
13106
- null,
13107
- 2
13108
- )
13109
- }
13110
- ]
13111
- };
13112
- }
13113
- );
13114
- server.tool(
13115
- "create_pr_stack",
13116
- "Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
13117
- {
13118
- repoPath: import_zod.z.string(),
13119
- branches: import_zod.z.array(import_zod.z.string()).min(1),
13120
- agent: import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
13121
- base: import_zod.z.string().optional(),
13122
- title: import_zod.z.string().optional()
13123
- },
13124
- async (args) => {
13125
- const result = await orch.createPrStack({
13126
- repoPath: args.repoPath,
13127
- branches: args.branches,
13128
- agent: args.agent,
13129
- base: args.base,
13130
- title: args.title
13131
- });
13132
- return {
13133
- content: [
13134
- {
13135
- type: "text",
13136
- text: JSON.stringify(
13137
- {
13138
- stackNumber: result.stack.stackNumber,
13139
- trunk: result.stack.trunk,
13140
- threads: result.threads.map((t) => ({
13141
- id: t.id,
13142
- title: t.title,
13143
- branchName: t.branchName,
13144
- stackLayer: t.stackLayer,
13145
- worktreePath: t.worktreePath,
13146
- link: `sideboard://thread/${t.id}`
13147
- }))
13148
- },
13149
- null,
13150
- 2
13151
- )
13152
- }
13153
- ]
13154
- };
13155
- }
13156
- );
13157
- server.tool(
13158
- "list_issues",
13159
- "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.",
13160
- { repoPath: import_zod.z.string() },
13161
- async ({ repoPath }) => {
13162
- const root = await resolveRepoRoot(repoPath);
13163
- const result = await listIssues(root);
13164
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
13165
- }
13166
- );
13167
- server.tool(
13168
- "list_linear_issues",
13169
- "Deprecated: prefer list_issues. Lists assigned Linear issues via the chosen agent MCP connector",
13170
- {
13171
- agent: import_zod.z.enum(["claude", "codex", "opencode"]),
13172
- repoPath: import_zod.z.string()
13173
- },
13174
- async ({ agent, repoPath }) => {
13175
- const issues = await listLinearIssues(agent, repoPath);
13176
- return { content: [{ type: "text", text: JSON.stringify(issues, null, 2) }] };
13177
- }
13178
- );
13550
+ );
13551
+ server.tool(
13552
+ "list_issues",
13553
+ "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.",
13554
+ { repoPath: import_zod.z.string() },
13555
+ async ({ repoPath }) => {
13556
+ const root = await resolveRepoRoot(repoPath);
13557
+ const result = await listIssues(root);
13558
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
13559
+ }
13560
+ );
13561
+ server.tool(
13562
+ "list_linear_issues",
13563
+ "Deprecated: prefer list_issues. Lists assigned Linear issues via the chosen agent MCP connector",
13564
+ {
13565
+ agent: import_zod.z.enum(["claude", "codex", "opencode"]),
13566
+ repoPath: import_zod.z.string()
13567
+ },
13568
+ async ({ agent, repoPath }) => {
13569
+ const issues = await listLinearIssues(agent, repoPath);
13570
+ return { content: [{ type: "text", text: JSON.stringify(issues, null, 2) }] };
13571
+ }
13572
+ );
13573
+ }
13179
13574
  const transport = new import_stdio.StdioServerTransport();
13180
13575
  await server.connect(transport);
13181
13576
  }