@sideboard-ai/core 0.1.62 → 0.1.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/{agents-KF3J3C53.js → agents-OTBZVTZO.js} +27 -7
  2. package/dist/{agents-QLSFIQAO.js → agents-TS3EI2YI.js} +27 -7
  3. package/dist/{app-settings-LTSFG7QJ.js → app-settings-GJVZGU3M.js} +13 -3
  4. package/dist/{app-settings-HK4KAM4R.js → app-settings-ONKMFSEJ.js} +13 -3
  5. package/dist/{chunk-UGXSSBFJ.js → chunk-4BUCGNZS.js} +1 -1
  6. package/dist/chunk-4UIYN56C.js +173 -0
  7. package/dist/{chunk-NVU7EC2E.js → chunk-57WWNMFP.js} +2 -2
  8. package/dist/{chunk-A6FOKNOU.js → chunk-5HTTCYWA.js} +2 -2
  9. package/dist/{chunk-DXTGDXO2.js → chunk-7ZOQY4A7.js} +31 -6
  10. package/dist/{chunk-ZF5YJCXO.js → chunk-B4PRRKMA.js} +284 -93
  11. package/dist/{chunk-DDN3UF4R.js → chunk-BPOTVI5J.js} +115 -3
  12. package/dist/{chunk-MBAL5FE6.js → chunk-DB3I75FI.js} +4 -4
  13. package/dist/chunk-FGDJOJ2L.js +176 -0
  14. package/dist/{chunk-2FEA4LDA.js → chunk-JWZNK3CQ.js} +31 -6
  15. package/dist/{chunk-2MCNURQV.js → chunk-K7LM4CLD.js} +279 -93
  16. package/dist/{chunk-3KKNTHUC.js → chunk-LW5CMQGZ.js} +1 -1
  17. package/dist/{chunk-JB2H7E7V.js → chunk-M5V4QIWF.js} +1 -1
  18. package/dist/{chunk-6CUJQKTJ.js → chunk-MHR4OGZ4.js} +115 -3
  19. package/dist/{chunk-46KMKHSD.js → chunk-WAFIF22N.js} +2 -2
  20. package/dist/{chunk-27MAWNKY.js → chunk-ZRFAZVGL.js} +2 -2
  21. package/dist/{connected-teams-MKIYKOYA.js → connected-teams-OMDHTL6A.js} +2 -2
  22. package/dist/{connected-teams-RSL4VB3X.js → connected-teams-RUNMOCG5.js} +2 -2
  23. package/dist/{coordinator-prompt-N2R6FGZK.js → coordinator-prompt-3BSMKZL4.js} +4 -4
  24. package/dist/{coordinator-prompt-LKG3SHMI.js → coordinator-prompt-6HMTOX4G.js} +4 -4
  25. package/dist/{global-workspace-LALWQWRJ.js → global-workspace-QS7A23CB.js} +5 -6
  26. package/dist/{global-workspace-YOAUQCMA.js → global-workspace-YZ2NJVLL.js} +5 -6
  27. package/dist/index.cjs +1345 -933
  28. package/dist/index.d.cts +96 -1
  29. package/dist/index.d.ts +96 -1
  30. package/dist/index.js +750 -715
  31. package/dist/mcp/run-stdio.cjs +1293 -977
  32. package/dist/mcp/run-stdio.js +691 -748
  33. package/dist/{run-GPSVV5OH.js → run-JLD2Y52J.js} +1 -1
  34. package/dist/{run-HZTBWGQR.js → run-Y2MI3EVV.js} +1 -1
  35. package/dist/{workspaces-E75POJXP.js → workspaces-AIUYUXB7.js} +6 -7
  36. package/dist/{workspaces-VCAIHVIE.js → workspaces-EBFW7I7S.js} +6 -7
  37. package/dist/{worktree-MV4WRW6G.js → worktree-7NBPIVFZ.js} +2 -2
  38. package/dist/{worktree-LZEGVQSN.js → worktree-WMDJW2BL.js} +2 -2
  39. package/package.json +1 -1
  40. package/dist/chunk-73AT5K4A.js +0 -104
  41. package/dist/chunk-GI7E5733.js +0 -30
  42. package/dist/chunk-RIWUU7FO.js +0 -107
  43. package/dist/chunk-S2LL5HA4.js +0 -33
@@ -1507,18 +1507,37 @@ var init_gh_errors = __esm({
1507
1507
  });
1508
1508
 
1509
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
+ }
1520
+ function conductorBundledBinDir(home = process.env.HOME || process.env.USERPROFILE || (0, import_node_os3.homedir)()) {
1521
+ return (0, import_node_path4.join)(home, "Library", "Application Support", "com.conductor.app", "bin");
1522
+ }
1523
+ function isConductorBundledCli(filePath) {
1524
+ const p = (filePath ?? "").replace(/\\/g, "/");
1525
+ return p.includes("/com.conductor.app/bin/") || p.endsWith("/com.conductor.app/bin");
1526
+ }
1510
1527
  function ensureAgentPath(env = process.env) {
1511
1528
  const home = env.HOME || env.USERPROFILE || (0, import_node_os3.homedir)();
1512
1529
  const current = env.PATH ?? "";
1513
1530
  const parts = current.split(import_node_path4.delimiter).filter(Boolean);
1514
1531
  const seen = new Set(parts);
1515
1532
  const extras = [
1516
- ...EXTRA_BIN_DIRS.map((rel) => `${home}/${rel}`),
1533
+ ...EXTRA_BIN_DIRS.map((rel) => (0, import_node_path4.join)(home, rel)),
1517
1534
  "/opt/homebrew/bin",
1518
- "/usr/local/bin"
1535
+ "/usr/local/bin",
1536
+ // Keep after Homebrew/npm so a user-installed CLI still wins.
1537
+ conductorBundledBinDir(home)
1519
1538
  ];
1520
1539
  for (const dir of extras.reverse()) {
1521
- if (!dir || seen.has(dir)) continue;
1540
+ if (!dir || seen.has(dir) || !(0, import_node_fs4.existsSync)(dir)) continue;
1522
1541
  parts.unshift(dir);
1523
1542
  seen.add(dir);
1524
1543
  }
@@ -1526,10 +1545,50 @@ function ensureAgentPath(env = process.env) {
1526
1545
  env.PATH = next;
1527
1546
  return next;
1528
1547
  }
1529
- var import_node_os3, import_node_path4, EXTRA_BIN_DIRS;
1548
+ function enrichPathWithNpmGlobalBin(env = process.env) {
1549
+ ensureAgentPath(env);
1550
+ try {
1551
+ const prefix = (0, import_node_child_process.execFileSync)("npm", ["prefix", "-g"], {
1552
+ encoding: "utf8",
1553
+ env: { ...process.env, ...env },
1554
+ timeout: 8e3,
1555
+ stdio: ["ignore", "pipe", "ignore"]
1556
+ }).trim().split(/\r?\n/).find(Boolean);
1557
+ if (prefix) {
1558
+ const binDir = process.platform === "win32" ? prefix : (0, import_node_path4.join)(prefix, "bin");
1559
+ prependPathDir(env, binDir);
1560
+ }
1561
+ } catch {
1562
+ }
1563
+ return env.PATH ?? "";
1564
+ }
1565
+ function posixShellSingleQuote(value) {
1566
+ return `'${value.replace(/'/g, `'\\''`)}'`;
1567
+ }
1568
+ function resolveCommandBinarySync(command, whichPath) {
1569
+ const trimmed = command.trim();
1570
+ if (!trimmed) return trimmed;
1571
+ const match = /^(\S+)(\s[\s\S]*)?$/.exec(trimmed);
1572
+ if (!match) return trimmed;
1573
+ const bin = match[1];
1574
+ const rest = match[2] ?? "";
1575
+ if (bin.includes("/") || bin.includes("\\")) return trimmed;
1576
+ const abs = whichPath?.trim().split(/\r?\n/).find(Boolean);
1577
+ if (!abs) return trimmed;
1578
+ return `${abs}${rest}`;
1579
+ }
1580
+ function withExportedPath(command, pathValue) {
1581
+ const trimmed = command.trim();
1582
+ if (!trimmed) return trimmed;
1583
+ if (/^(export\s+PATH=|PATH=)/.test(trimmed)) return trimmed;
1584
+ return `export PATH=${posixShellSingleQuote(pathValue)} && ${trimmed}`;
1585
+ }
1586
+ var import_node_fs4, import_node_child_process, import_node_os3, import_node_path4, EXTRA_BIN_DIRS;
1530
1587
  var init_path = __esm({
1531
1588
  "src/agents/path.ts"() {
1532
1589
  "use strict";
1590
+ import_node_fs4 = require("fs");
1591
+ import_node_child_process = require("child_process");
1533
1592
  import_node_os3 = require("os");
1534
1593
  import_node_path4 = require("path");
1535
1594
  EXTRA_BIN_DIRS = [
@@ -1537,6 +1596,10 @@ var init_path = __esm({
1537
1596
  ".cargo/bin",
1538
1597
  ".nvm/current/bin",
1539
1598
  ".asdf/shims",
1599
+ ".volta/bin",
1600
+ ".npm-global/bin",
1601
+ // fnm default alias (common when shell init is skipped)
1602
+ ".local/share/fnm/aliases/default/bin",
1540
1603
  // pnpm / npm global bins (where `@brightsy/cli` typically lands)
1541
1604
  "Library/pnpm",
1542
1605
  ".pnpm"
@@ -2596,7 +2659,7 @@ function isLocalPrFetchBranch(ref) {
2596
2659
  async function createThreadWorktree(opts) {
2597
2660
  let branchName = `thread/${opts.slug}`;
2598
2661
  const worktreePath = (0, import_node_path5.join)(worktreesRoot(opts.repoPath), opts.slug);
2599
- if ((0, import_node_fs4.existsSync)(worktreePath)) {
2662
+ if ((0, import_node_fs5.existsSync)(worktreePath)) {
2600
2663
  throw new Error(`Worktree already exists at ${worktreePath}`);
2601
2664
  }
2602
2665
  await ensureGhPreferOrigin(opts.repoPath);
@@ -2666,7 +2729,7 @@ async function createExistingBranchWorktree(opts) {
2666
2729
  const branchName = opts.branchName.trim();
2667
2730
  if (!branchName) throw new Error("branch name required");
2668
2731
  const worktreePath = (0, import_node_path5.join)(worktreesRoot(opts.repoPath), opts.slug);
2669
- if ((0, import_node_fs4.existsSync)(worktreePath)) {
2732
+ if ((0, import_node_fs5.existsSync)(worktreePath)) {
2670
2733
  throw new Error(`Worktree already exists at ${worktreePath}`);
2671
2734
  }
2672
2735
  await ensureGhPreferOrigin(opts.repoPath);
@@ -2927,9 +2990,9 @@ function sameRepoPath(a, b) {
2927
2990
  }
2928
2991
  function listLocalThreadBranchSlugs(repoPath) {
2929
2992
  const refsDir = (0, import_node_path5.join)(repoPath, ".git", "refs", "heads", "thread");
2930
- if (!(0, import_node_fs4.existsSync)(refsDir)) return [];
2993
+ if (!(0, import_node_fs5.existsSync)(refsDir)) return [];
2931
2994
  try {
2932
- return (0, import_node_fs4.readdirSync)(refsDir).filter((name) => !name.startsWith(".")).map((name) => normalizeTakenSlug(name));
2995
+ return (0, import_node_fs5.readdirSync)(refsDir).filter((name) => !name.startsWith(".")).map((name) => normalizeTakenSlug(name));
2933
2996
  } catch {
2934
2997
  return [];
2935
2998
  }
@@ -2937,8 +3000,8 @@ function listLocalThreadBranchSlugs(repoPath) {
2937
3000
  function collectTakenTeamSlugs(repoPath) {
2938
3001
  const taken = /* @__PURE__ */ new Set();
2939
3002
  const root = worktreesRoot(repoPath);
2940
- if ((0, import_node_fs4.existsSync)(root)) {
2941
- for (const entry of (0, import_node_fs4.readdirSync)(root, { withFileTypes: true })) {
3003
+ if ((0, import_node_fs5.existsSync)(root)) {
3004
+ for (const entry of (0, import_node_fs5.readdirSync)(root, { withFileTypes: true })) {
2942
3005
  if (entry.isDirectory() && entry.name !== ".DS_Store") {
2943
3006
  taken.add(normalizeTakenSlug(entry.name));
2944
3007
  }
@@ -2960,16 +3023,16 @@ function allocateTeamSlug(repoPath) {
2960
3023
  for (let attempt = 0; attempt < 32; attempt++) {
2961
3024
  const team = allocateTeamName(taken);
2962
3025
  const path = (0, import_node_path5.join)(worktreesRoot(repoPath), team.slug);
2963
- if (!(0, import_node_fs4.existsSync)(path)) return team;
3026
+ if (!(0, import_node_fs5.existsSync)(path)) return team;
2964
3027
  taken.add(team.slug);
2965
3028
  }
2966
3029
  throw new Error("No available soccer team worktree directories left");
2967
3030
  }
2968
- var import_node_fs4, import_node_path5;
3031
+ var import_node_fs5, import_node_path5;
2969
3032
  var init_worktree = __esm({
2970
3033
  "src/git/worktree.ts"() {
2971
3034
  "use strict";
2972
- import_node_fs4 = require("fs");
3035
+ import_node_fs5 = require("fs");
2973
3036
  import_node_path5 = require("path");
2974
3037
  init_paths();
2975
3038
  init_thread_store();
@@ -2997,6 +3060,7 @@ __export(app_settings_exports, {
2997
3060
  autoRunAfterSetupEnabled: () => autoRunAfterSetupEnabled,
2998
3061
  brightsyCloudConnectAgent: () => brightsyCloudConnectAgent,
2999
3062
  brightsyCloudConnectEnabled: () => brightsyCloudConnectEnabled,
3063
+ brightsyInjectWorktreeMcpEnabled: () => brightsyInjectWorktreeMcpEnabled,
3000
3064
  caffeinateWhileCloudConnectEnabled: () => caffeinateWhileCloudConnectEnabled,
3001
3065
  caffeinateWhileRunningEnabled: () => caffeinateWhileRunningEnabled,
3002
3066
  childEnvWithAppSettings: () => childEnvWithAppSettings,
@@ -3015,17 +3079,21 @@ __export(app_settings_exports, {
3015
3079
  maxConcurrentAgents: () => maxConcurrentAgents,
3016
3080
  orchestrationQuotaFallbackAgent: () => orchestrationQuotaFallbackAgent,
3017
3081
  orchestrationQuotaOnLimit: () => orchestrationQuotaOnLimit,
3082
+ resolveAgentExecutable: () => resolveAgentExecutable,
3018
3083
  resolveClaudeExecutable: () => resolveClaudeExecutable,
3019
3084
  resolveEffectiveIssueSource: () => resolveEffectiveIssueSource,
3020
3085
  resolveNewThreadOptions: () => resolveNewThreadOptions,
3021
3086
  resolveThreadDefaults: () => resolveThreadDefaults,
3022
3087
  saveAppSettings: () => saveAppSettings,
3023
3088
  updateAdvancedSettings: () => updateAdvancedSettings,
3089
+ updateAgentExecutable: () => updateAgentExecutable,
3024
3090
  updateAppEnvironment: () => updateAppEnvironment,
3025
3091
  updateBrightsySettings: () => updateBrightsySettings,
3026
3092
  updateClaudeSettings: () => updateClaudeSettings,
3093
+ updateCodexSettings: () => updateCodexSettings,
3027
3094
  updateDefaultsSettings: () => updateDefaultsSettings,
3028
- updateIntegrationsSettings: () => updateIntegrationsSettings
3095
+ updateIntegrationsSettings: () => updateIntegrationsSettings,
3096
+ updateOpencodeSettings: () => updateOpencodeSettings
3029
3097
  });
3030
3098
  function appSettingsPath() {
3031
3099
  return (0, import_node_path6.join)(appDataDir(), "settings.json");
@@ -3046,16 +3114,33 @@ function normalizeClaude(raw) {
3046
3114
  }
3047
3115
  return out;
3048
3116
  }
3117
+ function normalizeCliExecutable(raw) {
3118
+ if (!raw || typeof raw !== "object") return {};
3119
+ const source = raw;
3120
+ const out = {};
3121
+ if (typeof source.executablePath === "string") {
3122
+ const path = source.executablePath.trim();
3123
+ if (path) out.executablePath = path;
3124
+ }
3125
+ return out;
3126
+ }
3049
3127
  function normalizeBrightsy(raw) {
3050
3128
  if (!raw || typeof raw !== "object") return {};
3051
3129
  const source = raw;
3052
3130
  const out = {};
3131
+ if (typeof source.executablePath === "string") {
3132
+ const path = source.executablePath.trim();
3133
+ if (path) out.executablePath = path;
3134
+ }
3053
3135
  if (typeof source.cloudConnectEnabled === "boolean") {
3054
3136
  out.cloudConnectEnabled = source.cloudConnectEnabled;
3055
3137
  }
3056
3138
  if (typeof source.cloudConnectAgent === "string" && CLOUD_CONNECT_AGENTS.has(source.cloudConnectAgent)) {
3057
3139
  out.cloudConnectAgent = source.cloudConnectAgent;
3058
3140
  }
3141
+ if (typeof source.injectWorktreeMcp === "boolean") {
3142
+ out.injectWorktreeMcp = source.injectWorktreeMcp;
3143
+ }
3059
3144
  return out;
3060
3145
  }
3061
3146
  function normalizeIntegrations(raw) {
@@ -3141,6 +3226,8 @@ function normalizeAdvanced(raw) {
3141
3226
  function normalizeSettings(raw) {
3142
3227
  const env = {};
3143
3228
  let claude = {};
3229
+ let codex = {};
3230
+ let opencode = {};
3144
3231
  let brightsy = {};
3145
3232
  let integrations = {};
3146
3233
  let defaults = {};
@@ -3159,6 +3246,12 @@ function normalizeSettings(raw) {
3159
3246
  if ("claude" in raw) {
3160
3247
  claude = normalizeClaude(raw.claude);
3161
3248
  }
3249
+ if ("codex" in raw) {
3250
+ codex = normalizeCliExecutable(raw.codex);
3251
+ }
3252
+ if ("opencode" in raw) {
3253
+ opencode = normalizeCliExecutable(raw.opencode);
3254
+ }
3162
3255
  if ("brightsy" in raw) {
3163
3256
  brightsy = normalizeBrightsy(raw.brightsy);
3164
3257
  }
@@ -3174,15 +3267,24 @@ function normalizeSettings(raw) {
3174
3267
  advanced = normalizeAdvanced(raw.advanced);
3175
3268
  }
3176
3269
  }
3177
- return { environment: env, claude, brightsy, integrations, defaults, advanced };
3270
+ return {
3271
+ environment: env,
3272
+ claude,
3273
+ codex,
3274
+ opencode,
3275
+ brightsy,
3276
+ integrations,
3277
+ defaults,
3278
+ advanced
3279
+ };
3178
3280
  }
3179
3281
  function loadAppSettings() {
3180
3282
  const path = appSettingsPath();
3181
- if (!(0, import_node_fs5.existsSync)(path)) {
3283
+ if (!(0, import_node_fs6.existsSync)(path)) {
3182
3284
  return { ...EMPTY_SETTINGS };
3183
3285
  }
3184
3286
  try {
3185
- const parsed = JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
3287
+ const parsed = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
3186
3288
  return normalizeSettings(parsed);
3187
3289
  } catch {
3188
3290
  return { ...EMPTY_SETTINGS };
@@ -3190,8 +3292,8 @@ function loadAppSettings() {
3190
3292
  }
3191
3293
  function saveAppSettings(settings) {
3192
3294
  const normalized = normalizeSettings(settings);
3193
- (0, import_node_fs5.mkdirSync)(appDataDir(), { recursive: true });
3194
- (0, import_node_fs5.writeFileSync)(appSettingsPath(), `${JSON.stringify(normalized, null, 2)}
3295
+ (0, import_node_fs6.mkdirSync)(appDataDir(), { recursive: true });
3296
+ (0, import_node_fs6.writeFileSync)(appSettingsPath(), `${JSON.stringify(normalized, null, 2)}
3195
3297
  `, "utf8");
3196
3298
  return normalized;
3197
3299
  }
@@ -3227,6 +3329,13 @@ function updateClaudeSettings(patch) {
3227
3329
  function updateBrightsySettings(patch) {
3228
3330
  const current = loadAppSettings();
3229
3331
  const brightsy = { ...current.brightsy };
3332
+ if ("executablePath" in patch) {
3333
+ if (patch.executablePath == null || patch.executablePath.trim() === "") {
3334
+ delete brightsy.executablePath;
3335
+ } else {
3336
+ brightsy.executablePath = patch.executablePath.trim();
3337
+ }
3338
+ }
3230
3339
  if (typeof patch.cloudConnectEnabled === "boolean") {
3231
3340
  brightsy.cloudConnectEnabled = patch.cloudConnectEnabled;
3232
3341
  }
@@ -3237,8 +3346,35 @@ function updateBrightsySettings(patch) {
3237
3346
  brightsy.cloudConnectAgent = patch.cloudConnectAgent;
3238
3347
  }
3239
3348
  }
3349
+ if (typeof patch.injectWorktreeMcp === "boolean") {
3350
+ brightsy.injectWorktreeMcp = patch.injectWorktreeMcp;
3351
+ }
3240
3352
  return saveAppSettings({ ...current, brightsy });
3241
3353
  }
3354
+ function updateCodexSettings(patch) {
3355
+ const current = loadAppSettings();
3356
+ const codex = { ...current.codex };
3357
+ if ("executablePath" in patch) {
3358
+ if (patch.executablePath == null || patch.executablePath.trim() === "") {
3359
+ delete codex.executablePath;
3360
+ } else {
3361
+ codex.executablePath = patch.executablePath.trim();
3362
+ }
3363
+ }
3364
+ return saveAppSettings({ ...current, codex });
3365
+ }
3366
+ function updateOpencodeSettings(patch) {
3367
+ const current = loadAppSettings();
3368
+ const opencode = { ...current.opencode };
3369
+ if ("executablePath" in patch) {
3370
+ if (patch.executablePath == null || patch.executablePath.trim() === "") {
3371
+ delete opencode.executablePath;
3372
+ } else {
3373
+ opencode.executablePath = patch.executablePath.trim();
3374
+ }
3375
+ }
3376
+ return saveAppSettings({ ...current, opencode });
3377
+ }
3242
3378
  function updateIntegrationsSettings(patch) {
3243
3379
  const current = loadAppSettings();
3244
3380
  const integrations = { ...current.integrations };
@@ -3346,6 +3482,9 @@ function getLinearApiKey(settings = loadAppSettings()) {
3346
3482
  function brightsyCloudConnectEnabled(settings = loadAppSettings()) {
3347
3483
  return Boolean(settings.brightsy.cloudConnectEnabled);
3348
3484
  }
3485
+ function brightsyInjectWorktreeMcpEnabled(settings = loadAppSettings()) {
3486
+ return Boolean(settings.brightsy.injectWorktreeMcp);
3487
+ }
3349
3488
  function brightsyCloudConnectAgent(settings = loadAppSettings()) {
3350
3489
  const fallback = settings.brightsy.cloudConnectAgent && CLOUD_CONNECT_AGENTS.has(settings.brightsy.cloudConnectAgent) ? settings.brightsy.cloudConnectAgent : "claude";
3351
3490
  const preferred = getDefaultAgent(settings);
@@ -3440,8 +3579,36 @@ function maxConcurrentAgents(settings = loadAppSettings()) {
3440
3579
  return 3;
3441
3580
  }
3442
3581
  function resolveClaudeExecutable(settings = loadAppSettings()) {
3443
- const override = settings.claude.executablePath?.trim();
3444
- return override || "claude";
3582
+ return resolveAgentExecutable("claude", settings);
3583
+ }
3584
+ function resolveAgentExecutable(agent, settings = loadAppSettings()) {
3585
+ const fallback = DEFAULT_CLI_BIN[agent];
3586
+ if (agent === "claude") {
3587
+ const override2 = settings.claude.executablePath?.trim();
3588
+ return override2 || fallback;
3589
+ }
3590
+ if (agent === "codex") {
3591
+ const override2 = settings.codex.executablePath?.trim();
3592
+ return override2 || fallback;
3593
+ }
3594
+ if (agent === "opencode") {
3595
+ const override2 = settings.opencode.executablePath?.trim();
3596
+ return override2 || fallback;
3597
+ }
3598
+ const override = settings.brightsy.executablePath?.trim();
3599
+ return override || fallback;
3600
+ }
3601
+ function updateAgentExecutable(agent, executablePath) {
3602
+ if (agent === "claude") {
3603
+ return updateClaudeSettings({ executablePath });
3604
+ }
3605
+ if (agent === "codex") {
3606
+ return updateCodexSettings({ executablePath });
3607
+ }
3608
+ if (agent === "opencode") {
3609
+ return updateOpencodeSettings({ executablePath });
3610
+ }
3611
+ return updateBrightsySettings({ executablePath });
3445
3612
  }
3446
3613
  function claudeChromeEnabled(settings = loadAppSettings()) {
3447
3614
  return Boolean(settings.claude.chromeEnabled);
@@ -3469,11 +3636,11 @@ function childEnvWithAppSettings(extra) {
3469
3636
  function harnessEnvKey(harness) {
3470
3637
  return HARNESS_ENV_KEYS[harness];
3471
3638
  }
3472
- var import_node_fs5, import_node_os4, import_node_path6, HARNESS_ENV_KEYS, DEFAULT_AGENTS, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, EMPTY_SETTINGS;
3639
+ 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;
3473
3640
  var init_app_settings = __esm({
3474
3641
  "src/store/app-settings.ts"() {
3475
3642
  "use strict";
3476
- import_node_fs5 = require("fs");
3643
+ import_node_fs6 = require("fs");
3477
3644
  import_node_os4 = require("os");
3478
3645
  import_node_path6 = require("path");
3479
3646
  init_thinking_effort();
@@ -3502,11 +3669,19 @@ var init_app_settings = __esm({
3502
3669
  EMPTY_SETTINGS = {
3503
3670
  environment: {},
3504
3671
  claude: {},
3672
+ codex: {},
3673
+ opencode: {},
3505
3674
  brightsy: {},
3506
3675
  integrations: {},
3507
3676
  defaults: {},
3508
3677
  advanced: {}
3509
3678
  };
3679
+ DEFAULT_CLI_BIN = {
3680
+ claude: "claude",
3681
+ codex: "codex",
3682
+ opencode: "opencode",
3683
+ brightsy: "brightsy"
3684
+ };
3510
3685
  }
3511
3686
  });
3512
3687
 
@@ -3632,7 +3807,7 @@ function coordinatorTurnReminder(opts) {
3632
3807
  function ensureGlobalCoordinatorCwd(opts) {
3633
3808
  const dir = globalAgentCwd();
3634
3809
  try {
3635
- (0, import_node_fs6.mkdirSync)(dir, { recursive: true });
3810
+ (0, import_node_fs7.mkdirSync)(dir, { recursive: true });
3636
3811
  } catch {
3637
3812
  return dir;
3638
3813
  }
@@ -3640,7 +3815,7 @@ function ensureGlobalCoordinatorCwd(opts) {
3640
3815
  let orchId = opts?.orchestratorThreadId?.trim() || "";
3641
3816
  if (!orchId) {
3642
3817
  try {
3643
- const existing = (0, import_node_fs6.readFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), "utf8");
3818
+ const existing = (0, import_node_fs7.readFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), "utf8");
3644
3819
  const m = existing.match(
3645
3820
  /YOUR orchestration thread id is `([0-9a-f-]{36})`/i
3646
3821
  );
@@ -3682,9 +3857,9 @@ function ensureGlobalCoordinatorCwd(opts) {
3682
3857
  "Always ask worktree agents to open draft PRs (`send_to_thread` + `gh pr create --draft -R <origin>`); never open PRs from the orchestrator."
3683
3858
  ].join("\n");
3684
3859
  try {
3685
- (0, import_node_fs6.writeFileSync)((0, import_node_path7.join)(dir, "CLAUDE.md"), `${body}
3860
+ (0, import_node_fs7.writeFileSync)((0, import_node_path7.join)(dir, "CLAUDE.md"), `${body}
3686
3861
  `, "utf8");
3687
- (0, import_node_fs6.writeFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), `${body}
3862
+ (0, import_node_fs7.writeFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), `${body}
3688
3863
  `, "utf8");
3689
3864
  } catch {
3690
3865
  }
@@ -3717,11 +3892,11 @@ function coordinatorSystemPrompt(opts) {
3717
3892
  formatWorkspaceInventory(opts.workspaces)
3718
3893
  ].join("\n");
3719
3894
  }
3720
- var import_node_fs6, import_node_path7, COORDINATOR_TOOL_PLAYBOOK;
3895
+ var import_node_fs7, import_node_path7, COORDINATOR_TOOL_PLAYBOOK;
3721
3896
  var init_coordinator_prompt = __esm({
3722
3897
  "src/orchestrator/coordinator-prompt.ts"() {
3723
3898
  "use strict";
3724
- import_node_fs6 = require("fs");
3899
+ import_node_fs7 = require("fs");
3725
3900
  import_node_path7 = require("path");
3726
3901
  init_worktree();
3727
3902
  init_app_settings();
@@ -3947,26 +4122,26 @@ function brightsyConfigPath() {
3947
4122
  }
3948
4123
  function loadBrightsyConfig() {
3949
4124
  const path = brightsyConfigPath();
3950
- if (!(0, import_node_fs7.existsSync)(path)) {
4125
+ if (!(0, import_node_fs8.existsSync)(path)) {
3951
4126
  throw new Error("Brightsy not logged in \u2014 run `brightsy login` first");
3952
4127
  }
3953
- const raw = JSON.parse((0, import_node_fs7.readFileSync)(path, "utf8"));
4128
+ const raw = JSON.parse((0, import_node_fs8.readFileSync)(path, "utf8"));
3954
4129
  if (!raw.access_token || !raw.account_id) {
3955
4130
  throw new Error("Brightsy config incomplete \u2014 run `brightsy login`");
3956
4131
  }
3957
4132
  return raw;
3958
4133
  }
3959
4134
  function saveBrightsyConfig(cfg) {
3960
- (0, import_node_fs7.writeFileSync)(brightsyConfigPath(), `${JSON.stringify(cfg, null, 2)}
4135
+ (0, import_node_fs8.writeFileSync)(brightsyConfigPath(), `${JSON.stringify(cfg, null, 2)}
3961
4136
  `, {
3962
4137
  mode: 384
3963
4138
  });
3964
4139
  }
3965
- var import_node_fs7, import_node_os5, import_node_path8;
4140
+ var import_node_fs8, import_node_os5, import_node_path8;
3966
4141
  var init_config = __esm({
3967
4142
  "src/brightsy/config.ts"() {
3968
4143
  "use strict";
3969
- import_node_fs7 = require("fs");
4144
+ import_node_fs8 = require("fs");
3970
4145
  import_node_os5 = require("os");
3971
4146
  import_node_path8 = require("path");
3972
4147
  }
@@ -3987,18 +4162,18 @@ function storePath() {
3987
4162
  }
3988
4163
  function readStore() {
3989
4164
  const path = storePath();
3990
- if (!(0, import_node_fs8.existsSync)(path)) return [];
4165
+ if (!(0, import_node_fs9.existsSync)(path)) return [];
3991
4166
  try {
3992
- const parsed = JSON.parse((0, import_node_fs8.readFileSync)(path, "utf8"));
4167
+ const parsed = JSON.parse((0, import_node_fs9.readFileSync)(path, "utf8"));
3993
4168
  return Array.isArray(parsed.teams) ? parsed.teams : [];
3994
4169
  } catch {
3995
4170
  return [];
3996
4171
  }
3997
4172
  }
3998
4173
  function writeStore(teams) {
3999
- (0, import_node_fs8.mkdirSync)(appDataDir(), { recursive: true });
4174
+ (0, import_node_fs9.mkdirSync)(appDataDir(), { recursive: true });
4000
4175
  const path = storePath();
4001
- (0, import_node_fs8.writeFileSync)(path, `${JSON.stringify({ teams }, null, 2)}
4176
+ (0, import_node_fs9.writeFileSync)(path, `${JSON.stringify({ teams }, null, 2)}
4002
4177
  `, {
4003
4178
  mode: 384
4004
4179
  });
@@ -4110,11 +4285,11 @@ function brightsyMcpServerName(slug) {
4110
4285
  const cleaned = slug.replace(/[^A-Za-z0-9_-]/g, "_").replace(/^_+|_+$/g, "");
4111
4286
  return `brightsy_${cleaned || "team"}`;
4112
4287
  }
4113
- var import_node_fs8, import_node_path9;
4288
+ var import_node_fs9, import_node_path9;
4114
4289
  var init_connected_teams = __esm({
4115
4290
  "src/brightsy/connected-teams.ts"() {
4116
4291
  "use strict";
4117
- import_node_fs8 = require("fs");
4292
+ import_node_fs9 = require("fs");
4118
4293
  import_node_path9 = require("path");
4119
4294
  init_paths();
4120
4295
  init_accounts();
@@ -4162,6 +4337,11 @@ function normalizeTurnInput(input) {
4162
4337
  if (typeof input === "string") return { prompt: input };
4163
4338
  return { prompt: input.prompt, cachedPrefix: input.cachedPrefix?.trim() || void 0 };
4164
4339
  }
4340
+ function dropCachedPrefixOnResume(input, sessionId) {
4341
+ const turn = normalizeTurnInput(input);
4342
+ if (sessionId) return { prompt: turn.prompt };
4343
+ return turn;
4344
+ }
4165
4345
  function flattenTurnInput(input) {
4166
4346
  const { cachedPrefix, prompt } = normalizeTurnInput(input);
4167
4347
  if (!cachedPrefix) return prompt;
@@ -4306,9 +4486,13 @@ async function fetchTeamChatTargets(team) {
4306
4486
  };
4307
4487
  }
4308
4488
  async function listBrightsyChatTargetsViaCli() {
4309
- const listed = await run("brightsy", ["chat", "--list-targets", "--json"], {
4310
- reject: false
4311
- });
4489
+ const listed = await run(
4490
+ resolveAgentExecutable("brightsy"),
4491
+ ["chat", "--list-targets", "--json"],
4492
+ {
4493
+ reject: false
4494
+ }
4495
+ );
4312
4496
  if (listed.exitCode !== 0 || !listed.stdout.trim()) {
4313
4497
  throw new Error(
4314
4498
  listed.stderr.trim() || "Failed to list Brightsy chat targets \u2014 is `brightsy` installed and logged in?"
@@ -4392,13 +4576,15 @@ async function syncCliForTarget(accountId) {
4392
4576
  }
4393
4577
  applyConnectedTeamToCli(team);
4394
4578
  }
4395
- var brightsyAdapter;
4579
+ var import_node_fs10, brightsyAdapter;
4396
4580
  var init_brightsy = __esm({
4397
4581
  "src/agents/brightsy.ts"() {
4398
4582
  "use strict";
4583
+ import_node_fs10 = require("fs");
4399
4584
  init_run();
4400
4585
  init_connected_teams();
4401
4586
  init_config();
4587
+ init_app_settings();
4402
4588
  init_brightsy_targets();
4403
4589
  init_error_detail();
4404
4590
  init_turn_input();
@@ -4406,18 +4592,32 @@ var init_brightsy = __esm({
4406
4592
  brightsyAdapter = {
4407
4593
  kind: "brightsy",
4408
4594
  async detect() {
4409
- const which = await run("which", ["brightsy"], { reject: false });
4410
- if (which.exitCode !== 0) {
4411
- return {
4412
- agent: "brightsy",
4413
- installed: false,
4414
- authenticated: false,
4415
- linearMcp: false,
4416
- warnings: [],
4417
- reason: "brightsy CLI not found on PATH \u2014 npm i -g @brightsy/cli"
4418
- };
4595
+ const brightsy = resolveAgentExecutable("brightsy");
4596
+ if (brightsy !== "brightsy") {
4597
+ if (!(0, import_node_fs10.existsSync)(brightsy)) {
4598
+ return {
4599
+ agent: "brightsy",
4600
+ installed: false,
4601
+ authenticated: false,
4602
+ linearMcp: false,
4603
+ warnings: [],
4604
+ reason: `Brightsy executable not found: ${brightsy}`
4605
+ };
4606
+ }
4607
+ } else {
4608
+ const which = await run("which", ["brightsy"], { reject: false });
4609
+ if (which.exitCode !== 0) {
4610
+ return {
4611
+ agent: "brightsy",
4612
+ installed: false,
4613
+ authenticated: false,
4614
+ linearMcp: false,
4615
+ warnings: [],
4616
+ reason: "brightsy CLI not found on PATH \u2014 npm i -g @brightsy/cli"
4617
+ };
4618
+ }
4419
4619
  }
4420
- const who = await run("brightsy", ["whoami"], { reject: false });
4620
+ const who = await run(brightsy, ["whoami"], { reject: false });
4421
4621
  const authenticated = who.exitCode === 0 && /logged in as/i.test(who.stdout);
4422
4622
  return {
4423
4623
  agent: "brightsy",
@@ -4442,7 +4642,7 @@ var init_brightsy = __esm({
4442
4642
  target.id
4443
4643
  ];
4444
4644
  return {
4445
- file: "brightsy",
4645
+ file: resolveAgentExecutable("brightsy"),
4446
4646
  args,
4447
4647
  cwd: thread.worktreePath,
4448
4648
  // Piped stdin becomes the message body (avoids ARG_MAX for long seeds).
@@ -4459,7 +4659,11 @@ var init_brightsy = __esm({
4459
4659
  const target = decodeBrightsyTarget(thread.model);
4460
4660
  await syncCliForTarget(target.accountId);
4461
4661
  const args = target.type === "model" ? ["chat", "--model", target.id] : ["chat", "--agent", target.id];
4462
- return { file: "brightsy", args, cwd: thread.worktreePath };
4662
+ return {
4663
+ file: resolveAgentExecutable("brightsy"),
4664
+ args,
4665
+ cwd: thread.worktreePath
4666
+ };
4463
4667
  }
4464
4668
  };
4465
4669
  }
@@ -4504,6 +4708,18 @@ var init_claude_mcp = __esm({
4504
4708
  }
4505
4709
  });
4506
4710
 
4711
+ // src/mcp/profile.ts
4712
+ function sideboardMcpProfile(env = process.env) {
4713
+ return env[SIDEBOARD_MCP_PROFILE_ENV]?.trim().toLowerCase() === "worktree" ? "worktree" : "orchestration";
4714
+ }
4715
+ var SIDEBOARD_MCP_PROFILE_ENV;
4716
+ var init_profile = __esm({
4717
+ "src/mcp/profile.ts"() {
4718
+ "use strict";
4719
+ SIDEBOARD_MCP_PROFILE_ENV = "SIDEBOARD_MCP_PROFILE";
4720
+ }
4721
+ });
4722
+
4507
4723
  // src/agents/node-launch.ts
4508
4724
  function isAsarPath(filePath) {
4509
4725
  return /\.asar([/\\]|$)/.test(filePath);
@@ -4551,6 +4767,38 @@ function isBrightsyConnected() {
4551
4767
  return false;
4552
4768
  }
4553
4769
  }
4770
+ function promptMentionsBrightsy(text) {
4771
+ return BRIGHTSY_WORD.test(text ?? "");
4772
+ }
4773
+ function isBrightsyMcpToolName(name) {
4774
+ const n = name.toLowerCase();
4775
+ return n === "brightsy" || n.startsWith("mcp__brightsy") || n.startsWith("brightsy_");
4776
+ }
4777
+ function toolInputUsesBrightsyDatasource(input) {
4778
+ const ds = input?.datasource;
4779
+ return typeof ds === "string" && ds.toLowerCase() === "brightsy";
4780
+ }
4781
+ function messageUsedBrightsyMcp(msg) {
4782
+ if (msg.role === "user" && promptMentionsBrightsy(msg.text)) return true;
4783
+ for (const part of msg.parts ?? []) {
4784
+ if (part.type !== "tool") continue;
4785
+ if (isBrightsyMcpToolName(part.name)) return true;
4786
+ if (/present_(schema|files)$/i.test(part.name) && toolInputUsesBrightsyDatasource(part.input)) {
4787
+ return true;
4788
+ }
4789
+ }
4790
+ return false;
4791
+ }
4792
+ function threadRequestsBrightsyMcp(thread) {
4793
+ return (thread?.messages ?? []).some(messageUsedBrightsyMcp);
4794
+ }
4795
+ function shouldInjectBrightsyMcp(thread, opts) {
4796
+ if (!(opts?.connected ?? isBrightsyConnected())) return false;
4797
+ if (opts?.orchestrator) return true;
4798
+ const alwaysOn = opts?.alwaysOnWorktree ?? Boolean(loadAppSettings().brightsy?.injectWorktreeMcp);
4799
+ if (alwaysOn) return true;
4800
+ return threadRequestsBrightsyMcp(thread);
4801
+ }
4554
4802
  function mcpLaunch(cmd, name, env) {
4555
4803
  if (cmd === "brightsy-mcp") {
4556
4804
  return { name, command: "brightsy-mcp", ...env ? { env } : {} };
@@ -4596,7 +4844,7 @@ function corePackageDir() {
4596
4844
  }
4597
4845
  function findSideboardMcpJsEntry() {
4598
4846
  const override = process.env.SIDEBOARD_MCP_ENTRY?.trim() || process.env.SIDEBOARD_CLI?.trim();
4599
- if (override && (0, import_node_fs9.existsSync)(override)) return override;
4847
+ if (override && (0, import_node_fs11.existsSync)(override)) return override;
4600
4848
  let dir = corePackageDir();
4601
4849
  for (let i = 0; i < 10; i++) {
4602
4850
  const candidates = [
@@ -4609,7 +4857,7 @@ function findSideboardMcpJsEntry() {
4609
4857
  (0, import_node_path10.join)(dir, "cli/dist/index.js")
4610
4858
  ];
4611
4859
  for (const p of candidates) {
4612
- if ((0, import_node_fs9.existsSync)(p)) return p;
4860
+ if ((0, import_node_fs11.existsSync)(p)) return p;
4613
4861
  }
4614
4862
  const parent = (0, import_node_path10.dirname)(dir);
4615
4863
  if (parent === dir) break;
@@ -4640,11 +4888,13 @@ async function buildInjectedMcpServers(opts) {
4640
4888
  const servers = [];
4641
4889
  if (opts.includeSideboard) {
4642
4890
  const sideboard = await resolveSideboardMcpServer();
4891
+ const orchId = opts.orchestratorThreadId?.trim();
4892
+ const profile = orchId ? "orchestration" : "worktree";
4643
4893
  sideboard.env = {
4644
4894
  ...sideboard.env ?? {},
4645
- SIDEBOARD_APP_DATA: appDataDir()
4895
+ SIDEBOARD_APP_DATA: appDataDir(),
4896
+ [SIDEBOARD_MCP_PROFILE_ENV]: profile
4646
4897
  };
4647
- const orchId = opts.orchestratorThreadId?.trim();
4648
4898
  if (orchId) {
4649
4899
  sideboard.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID = orchId;
4650
4900
  }
@@ -4719,16 +4969,16 @@ function writeMcpServersConfig(servers) {
4719
4969
  ...s.env ? { env: s.env } : {}
4720
4970
  };
4721
4971
  }
4722
- const dir = (0, import_node_fs9.mkdtempSync)((0, import_node_path10.join)((0, import_node_os6.tmpdir)(), "sideboard-mcp-"));
4972
+ const dir = (0, import_node_fs11.mkdtempSync)((0, import_node_path10.join)((0, import_node_os6.tmpdir)(), "sideboard-mcp-"));
4723
4973
  const cfgPath = (0, import_node_path10.join)(dir, "mcp.json");
4724
- (0, import_node_fs9.writeFileSync)(cfgPath, JSON.stringify({ mcpServers }, null, 2));
4974
+ (0, import_node_fs11.writeFileSync)(cfgPath, JSON.stringify({ mcpServers }, null, 2));
4725
4975
  return cfgPath;
4726
4976
  }
4727
- 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;
4977
+ 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;
4728
4978
  var init_injected_mcp = __esm({
4729
4979
  "src/agents/injected-mcp.ts"() {
4730
4980
  "use strict";
4731
- import_node_fs9 = require("fs");
4981
+ import_node_fs11 = require("fs");
4732
4982
  import_node_module = require("module");
4733
4983
  import_node_os6 = require("os");
4734
4984
  import_node_path10 = require("path");
@@ -4736,6 +4986,8 @@ var init_injected_mcp = __esm({
4736
4986
  init_run();
4737
4987
  init_config();
4738
4988
  init_connected_teams();
4989
+ init_profile();
4990
+ init_app_settings();
4739
4991
  init_paths();
4740
4992
  init_node_launch();
4741
4993
  import_meta = {};
@@ -4751,6 +5003,7 @@ var init_injected_mcp = __esm({
4751
5003
  "mcp__sideboard__present_plan"
4752
5004
  ];
4753
5005
  brightsyMcpCommandCache = null;
5006
+ BRIGHTSY_WORD = /(?<![a-z0-9_-])brightsy(?![a-z0-9_-])/i;
4754
5007
  }
4755
5008
  });
4756
5009
 
@@ -4905,11 +5158,11 @@ function parseIssuesJson(raw) {
4905
5158
  }
4906
5159
  return [];
4907
5160
  }
4908
- var import_node_fs10, BASE_ALLOWED_TOOLS, CLAUDE_CHROME_ALLOWED_TOOLS, CLAUDE_PROMPT_ARG_MAX, claudeAdapter;
5161
+ var import_node_fs12, BASE_ALLOWED_TOOLS, CLAUDE_CHROME_ALLOWED_TOOLS, CLAUDE_PROMPT_ARG_MAX, claudeAdapter;
4909
5162
  var init_claude = __esm({
4910
5163
  "src/agents/claude.ts"() {
4911
5164
  "use strict";
4912
- import_node_fs10 = require("fs");
5165
+ import_node_fs12 = require("fs");
4913
5166
  init_run();
4914
5167
  init_app_settings();
4915
5168
  init_claude_mcp();
@@ -4938,7 +5191,7 @@ var init_claude = __esm({
4938
5191
  async detect() {
4939
5192
  const claude = resolveClaudeExecutable();
4940
5193
  if (claude !== "claude") {
4941
- if (!(0, import_node_fs10.existsSync)(claude)) {
5194
+ if (!(0, import_node_fs12.existsSync)(claude)) {
4942
5195
  return {
4943
5196
  agent: "claude",
4944
5197
  installed: false,
@@ -4977,9 +5230,8 @@ var init_claude = __esm({
4977
5230
  },
4978
5231
  async buildTurn(thread, input) {
4979
5232
  const claude = resolveClaudeExecutable();
4980
- const turn = normalizeTurnInput(input);
4981
5233
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
4982
- const effective = sessionId ? { prompt: turn.prompt } : turn;
5234
+ const effective = dropCachedPrefixOnResume(input, sessionId);
4983
5235
  const promptText = flattenTurnInput(effective);
4984
5236
  const useStdin = promptText.length > CLAUDE_PROMPT_ARG_MAX;
4985
5237
  if (process.env.SIDEBOARD_DEBUG_CLAUDE_TURN === "1") {
@@ -4992,7 +5244,9 @@ var init_claude = __esm({
4992
5244
  const isOrchestrator = isOrchestratorThread2(thread);
4993
5245
  const injectedServers = await buildInjectedMcpServers({
4994
5246
  includeSideboard: true,
4995
- includeBrightsy: isBrightsyConnected(),
5247
+ includeBrightsy: shouldInjectBrightsyMcp(thread, {
5248
+ orchestrator: isOrchestrator
5249
+ }),
4996
5250
  orchestratorThreadId: isOrchestrator ? thread.id : null
4997
5251
  });
4998
5252
  const injectedBrightsyNames = injectedServers.filter((s) => s.name === "brightsy" || s.name.startsWith("brightsy_")).map((s) => s.name);
@@ -5166,9 +5420,14 @@ async function listCodexModels() {
5166
5420
  if (cachedCodexModels && now - cachedCodexModels.at < CODEX_MODEL_CACHE_MS) {
5167
5421
  return cachedCodexModels.models;
5168
5422
  }
5169
- const which = await run("which", ["codex"], { reject: false });
5170
- if (which.exitCode !== 0) return FALLBACK_CODEX_MODELS;
5171
- const listed = await run("codex", ["debug", "models"], { reject: false });
5423
+ const codex = resolveAgentExecutable("codex");
5424
+ if (codex === "codex") {
5425
+ const which = await run("which", ["codex"], { reject: false });
5426
+ if (which.exitCode !== 0) return FALLBACK_CODEX_MODELS;
5427
+ } else if (!(0, import_node_fs13.existsSync)(codex)) {
5428
+ return FALLBACK_CODEX_MODELS;
5429
+ }
5430
+ const listed = await run(codex, ["debug", "models"], { reject: false });
5172
5431
  if (listed.exitCode !== 0 || !listed.stdout.trim()) {
5173
5432
  return cachedCodexModels?.models ?? FALLBACK_CODEX_MODELS;
5174
5433
  }
@@ -5207,8 +5466,8 @@ function codexConfigHasNetworkAccess() {
5207
5466
  (0, import_node_path11.join)((0, import_node_os7.homedir)(), ".config", "codex", "config.toml")
5208
5467
  ];
5209
5468
  for (const path of candidates) {
5210
- if (!(0, import_node_fs11.existsSync)(path)) continue;
5211
- const text = (0, import_node_fs11.readFileSync)(path, "utf8");
5469
+ if (!(0, import_node_fs13.existsSync)(path)) continue;
5470
+ const text = (0, import_node_fs13.readFileSync)(path, "utf8");
5212
5471
  if (/network_access\s*=\s*true/.test(text)) return true;
5213
5472
  }
5214
5473
  return false;
@@ -5241,21 +5500,23 @@ function asRecord(value) {
5241
5500
  }
5242
5501
  function codexLooksAuthenticated() {
5243
5502
  const authPath = (0, import_node_path11.join)((0, import_node_os7.homedir)(), ".codex", "auth.json");
5244
- if (!(0, import_node_fs11.existsSync)(authPath)) return false;
5503
+ if (!(0, import_node_fs13.existsSync)(authPath)) return false;
5245
5504
  try {
5246
- return (0, import_node_fs11.statSync)(authPath).size > 2;
5505
+ return (0, import_node_fs13.statSync)(authPath).size > 2;
5247
5506
  } catch {
5248
5507
  return false;
5249
5508
  }
5250
5509
  }
5251
- var import_node_fs11, import_node_os7, import_node_path11, CODEX_PROMPT_ARG_MAX, FALLBACK_CODEX_MODELS, cachedCodexModels, CODEX_MODEL_CACHE_MS, codexAdapter;
5510
+ var import_node_fs13, import_node_os7, import_node_path11, CODEX_PROMPT_ARG_MAX, FALLBACK_CODEX_MODELS, cachedCodexModels, CODEX_MODEL_CACHE_MS, codexAdapter;
5252
5511
  var init_codex = __esm({
5253
5512
  "src/agents/codex.ts"() {
5254
5513
  "use strict";
5255
- import_node_fs11 = require("fs");
5514
+ import_node_fs13 = require("fs");
5256
5515
  import_node_os7 = require("os");
5257
5516
  import_node_path11 = require("path");
5258
5517
  init_run();
5518
+ init_app_settings();
5519
+ init_global_workspace();
5259
5520
  init_error_detail();
5260
5521
  init_injected_mcp();
5261
5522
  init_turn_input();
@@ -5273,16 +5534,30 @@ var init_codex = __esm({
5273
5534
  codexAdapter = {
5274
5535
  kind: "codex",
5275
5536
  async detect() {
5276
- const which = await run("which", ["codex"], { reject: false, timeoutMs: 3e3 });
5277
- if (which.exitCode !== 0) {
5278
- return {
5279
- agent: "codex",
5280
- installed: false,
5281
- authenticated: false,
5282
- linearMcp: false,
5283
- warnings: [],
5284
- reason: "codex CLI not found on PATH"
5285
- };
5537
+ const codex = resolveAgentExecutable("codex");
5538
+ if (codex !== "codex") {
5539
+ if (!(0, import_node_fs13.existsSync)(codex)) {
5540
+ return {
5541
+ agent: "codex",
5542
+ installed: false,
5543
+ authenticated: false,
5544
+ linearMcp: false,
5545
+ warnings: [],
5546
+ reason: `Codex executable not found: ${codex}`
5547
+ };
5548
+ }
5549
+ } else {
5550
+ const which = await run("which", ["codex"], { reject: false, timeoutMs: 3e3 });
5551
+ if (which.exitCode !== 0) {
5552
+ return {
5553
+ agent: "codex",
5554
+ installed: false,
5555
+ authenticated: false,
5556
+ linearMcp: false,
5557
+ warnings: [],
5558
+ reason: "codex CLI not found on PATH"
5559
+ };
5560
+ }
5286
5561
  }
5287
5562
  const authenticated = codexLooksAuthenticated();
5288
5563
  const linearMcp = false;
@@ -5302,22 +5577,24 @@ var init_codex = __esm({
5302
5577
  };
5303
5578
  },
5304
5579
  async buildTurn(thread, input) {
5305
- const prompt = flattenTurnInput(input);
5306
5580
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
5581
+ const prompt = flattenTurnInput(dropCachedPrefixOnResume(input, sessionId));
5307
5582
  const useStdin = prompt.length > CODEX_PROMPT_ARG_MAX;
5308
5583
  const promptArg = useStdin ? "-" : prompt;
5309
5584
  if (process.env.SIDEBOARD_DEBUG_CODEX_TURN === "1") {
5310
- const { cachedPrefix } = normalizeTurnInput(input);
5311
5585
  console.error(
5312
- `[sideboard/codex] promptChars=${prompt.length} resumed=${Boolean(sessionId)} stdin=${useStdin} hasPrefix=${Boolean(cachedPrefix)}`
5586
+ `[sideboard/codex] promptChars=${prompt.length} resumed=${Boolean(sessionId)} stdin=${useStdin} hasPrefix=${prompt.includes("Current request:")}`
5313
5587
  );
5314
5588
  }
5315
5589
  const mode = permissionMode(thread);
5316
5590
  const model = thread.model?.trim();
5591
+ const isOrchestrator = isOrchestratorThread(thread);
5317
5592
  const injected = await buildInjectedMcpServers({
5318
5593
  includeSideboard: true,
5319
- includeBrightsy: isBrightsyConnected(),
5320
- orchestratorThreadId: thread.sourceType === "orchestration" ? thread.id : null
5594
+ includeBrightsy: shouldInjectBrightsyMcp(thread, {
5595
+ orchestrator: isOrchestrator
5596
+ }),
5597
+ orchestratorThreadId: isOrchestrator ? thread.id : null
5321
5598
  });
5322
5599
  const mcpOverrides = toCodexMcpConfigArgs(injected);
5323
5600
  const execOpts = [
@@ -5337,7 +5614,7 @@ var init_codex = __esm({
5337
5614
  ];
5338
5615
  const args = sessionId ? ["exec", ...execOpts, "resume", sessionId, promptArg] : ["exec", ...execOpts, promptArg];
5339
5616
  return {
5340
- file: "codex",
5617
+ file: resolveAgentExecutable("codex"),
5341
5618
  args,
5342
5619
  cwd: thread.worktreePath,
5343
5620
  stdin: useStdin ? `${prompt}
@@ -5433,16 +5710,17 @@ var init_codex = __esm({
5433
5710
  return cached;
5434
5711
  },
5435
5712
  async buildAttach(thread) {
5713
+ const codex = resolveAgentExecutable("codex");
5436
5714
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
5437
5715
  if (sessionId) {
5438
5716
  return {
5439
- file: "codex",
5717
+ file: codex,
5440
5718
  args: ["exec", "--cd", thread.worktreePath, "resume", sessionId],
5441
5719
  cwd: thread.worktreePath
5442
5720
  };
5443
5721
  }
5444
5722
  return {
5445
- file: "codex",
5723
+ file: codex,
5446
5724
  args: ["--cd", thread.worktreePath],
5447
5725
  cwd: thread.worktreePath
5448
5726
  };
@@ -5450,7 +5728,7 @@ var init_codex = __esm({
5450
5728
  async listLinearIssues(_repoPath) {
5451
5729
  const prompt = "List my assigned Linear issues as JSON array only: id, identifier, title, url, labels.";
5452
5730
  const { stdout, exitCode } = await run(
5453
- "codex",
5731
+ resolveAgentExecutable("codex"),
5454
5732
  [
5455
5733
  "exec",
5456
5734
  "--json",
@@ -5713,21 +5991,22 @@ function cursorRunnerPath() {
5713
5991
  (0, import_node_path12.join)(root, "src", "agents", "cursor-runner.ts")
5714
5992
  ];
5715
5993
  for (const candidate of candidates) {
5716
- if ((0, import_node_fs12.existsSync)(candidate)) return candidate;
5994
+ if ((0, import_node_fs14.existsSync)(candidate)) return candidate;
5717
5995
  }
5718
5996
  return candidates[0];
5719
5997
  }
5720
- 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;
5998
+ 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;
5721
5999
  var init_cursor = __esm({
5722
6000
  "src/agents/cursor.ts"() {
5723
6001
  "use strict";
5724
- import_node_fs12 = require("fs");
6002
+ import_node_fs14 = require("fs");
5725
6003
  import_node_module2 = require("module");
5726
6004
  import_node_path12 = require("path");
5727
6005
  import_node_url2 = require("url");
5728
6006
  import_sdk = require("@cursor/sdk");
5729
6007
  init_run();
5730
6008
  init_app_settings();
6009
+ init_global_workspace();
5731
6010
  init_cursor_events();
5732
6011
  init_injected_mcp();
5733
6012
  init_node_launch();
@@ -5764,13 +6043,16 @@ var init_cursor = __esm({
5764
6043
  };
5765
6044
  },
5766
6045
  async buildTurn(thread, input) {
5767
- const prompt = flattenTurnInput(input);
5768
6046
  const agentId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
6047
+ const prompt = flattenTurnInput(dropCachedPrefixOnResume(input, agentId));
5769
6048
  const apiKey = resolveCursorApiKey() || void 0;
6049
+ const isOrchestrator = isOrchestratorThread(thread);
5770
6050
  const injected = await buildInjectedMcpServers({
5771
6051
  includeSideboard: true,
5772
- includeBrightsy: isBrightsyConnected(),
5773
- orchestratorThreadId: thread.sourceType === "orchestration" ? thread.id : null
6052
+ includeBrightsy: shouldInjectBrightsyMcp(thread, {
6053
+ orchestrator: isOrchestrator
6054
+ }),
6055
+ orchestratorThreadId: isOrchestrator ? thread.id : null
5774
6056
  });
5775
6057
  const mcpServers = toCursorMcpServers(injected);
5776
6058
  const req = {
@@ -5841,9 +6123,14 @@ async function listOpencodeModels() {
5841
6123
  if (cachedOpencodeModels && now - cachedOpencodeModels.at < OPENCODE_MODEL_CACHE_MS) {
5842
6124
  return cachedOpencodeModels.models;
5843
6125
  }
5844
- const which = await run("which", ["opencode"], { reject: false });
5845
- if (which.exitCode !== 0) return FALLBACK_OPENCODE_MODELS;
5846
- const listed = await run("opencode", ["models"], { reject: false });
6126
+ const opencode = resolveAgentExecutable("opencode");
6127
+ if (opencode === "opencode") {
6128
+ const which = await run("which", ["opencode"], { reject: false });
6129
+ if (which.exitCode !== 0) return FALLBACK_OPENCODE_MODELS;
6130
+ } else if (!(0, import_node_fs15.existsSync)(opencode)) {
6131
+ return FALLBACK_OPENCODE_MODELS;
6132
+ }
6133
+ const listed = await run(opencode, ["models"], { reject: false });
5847
6134
  if (listed.exitCode !== 0 || !listed.stdout.trim()) {
5848
6135
  return cachedOpencodeModels?.models ?? FALLBACK_OPENCODE_MODELS;
5849
6136
  }
@@ -5870,11 +6157,14 @@ function usageFromOpencode(tokens) {
5870
6157
  cacheWriteTokens: tokens.cache?.write ? Number(tokens.cache.write) : void 0
5871
6158
  };
5872
6159
  }
5873
- var FALLBACK_OPENCODE_MODELS, cachedOpencodeModels, OPENCODE_MODEL_CACHE_MS, opencodeAdapter;
6160
+ var import_node_fs15, FALLBACK_OPENCODE_MODELS, cachedOpencodeModels, OPENCODE_MODEL_CACHE_MS, opencodeAdapter;
5874
6161
  var init_opencode = __esm({
5875
6162
  "src/agents/opencode.ts"() {
5876
6163
  "use strict";
6164
+ import_node_fs15 = require("fs");
5877
6165
  init_run();
6166
+ init_app_settings();
6167
+ init_global_workspace();
5878
6168
  init_error_detail();
5879
6169
  init_injected_mcp();
5880
6170
  init_turn_input();
@@ -5895,20 +6185,34 @@ var init_opencode = __esm({
5895
6185
  opencodeAdapter = {
5896
6186
  kind: "opencode",
5897
6187
  async detect() {
5898
- const which = await run("which", ["opencode"], { reject: false });
5899
- if (which.exitCode !== 0) {
5900
- return {
5901
- agent: "opencode",
5902
- installed: false,
5903
- authenticated: false,
5904
- linearMcp: false,
5905
- warnings: [],
5906
- reason: "opencode CLI not found on PATH"
5907
- };
6188
+ const opencode = resolveAgentExecutable("opencode");
6189
+ if (opencode !== "opencode") {
6190
+ if (!(0, import_node_fs15.existsSync)(opencode)) {
6191
+ return {
6192
+ agent: "opencode",
6193
+ installed: false,
6194
+ authenticated: false,
6195
+ linearMcp: false,
6196
+ warnings: [],
6197
+ reason: `OpenCode executable not found: ${opencode}`
6198
+ };
6199
+ }
6200
+ } else {
6201
+ const which = await run("which", ["opencode"], { reject: false });
6202
+ if (which.exitCode !== 0) {
6203
+ return {
6204
+ agent: "opencode",
6205
+ installed: false,
6206
+ authenticated: false,
6207
+ linearMcp: false,
6208
+ warnings: [],
6209
+ reason: "opencode CLI not found on PATH"
6210
+ };
6211
+ }
5908
6212
  }
5909
- const auth = await run("opencode", ["auth", "list"], { reject: false });
6213
+ const auth = await run(opencode, ["auth", "list"], { reject: false });
5910
6214
  const authenticated = auth.exitCode === 0 && auth.stdout.trim().length > 0;
5911
- const mcp = await run("opencode", ["mcp", "list"], { reject: false });
6215
+ const mcp = await run(opencode, ["mcp", "list"], { reject: false });
5912
6216
  const linearMcp = /linear/i.test(mcp.stdout + mcp.stderr);
5913
6217
  return {
5914
6218
  agent: "opencode",
@@ -5920,8 +6224,8 @@ var init_opencode = __esm({
5920
6224
  };
5921
6225
  },
5922
6226
  async buildTurn(thread, input) {
5923
- const prompt = flattenTurnInput(input);
5924
6227
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
6228
+ const prompt = flattenTurnInput(dropCachedPrefixOnResume(input, sessionId));
5925
6229
  const mode = permissionMode(thread);
5926
6230
  const model = thread.model?.trim();
5927
6231
  const args = [
@@ -5937,14 +6241,17 @@ var init_opencode = __esm({
5937
6241
  if (model) {
5938
6242
  args.push("--model", model);
5939
6243
  }
6244
+ const isOrchestrator = isOrchestratorThread(thread);
5940
6245
  const injected = await buildInjectedMcpServers({
5941
6246
  includeSideboard: true,
5942
- includeBrightsy: isBrightsyConnected(),
5943
- orchestratorThreadId: thread.sourceType === "orchestration" ? thread.id : null
6247
+ includeBrightsy: shouldInjectBrightsyMcp(thread, {
6248
+ orchestrator: isOrchestrator
6249
+ }),
6250
+ orchestratorThreadId: isOrchestrator ? thread.id : null
5944
6251
  });
5945
6252
  const mcpContent = injected.length > 0 ? toOpencodeMcpConfigContent(injected) : null;
5946
6253
  return {
5947
- file: "opencode",
6254
+ file: resolveAgentExecutable("opencode"),
5948
6255
  args,
5949
6256
  cwd: thread.worktreePath,
5950
6257
  // `opencode run` treats non-TTY stdin as the message body when no positional
@@ -6020,7 +6327,7 @@ var init_opencode = __esm({
6020
6327
  async resolveSessionId(worktreePath, cached) {
6021
6328
  const cachedId = cached?.trim() || null;
6022
6329
  const listed = await run(
6023
- "opencode",
6330
+ resolveAgentExecutable("opencode"),
6024
6331
  ["session", "list", "--format", "json"],
6025
6332
  { cwd: worktreePath, reject: false }
6026
6333
  );
@@ -6051,7 +6358,7 @@ var init_opencode = __esm({
6051
6358
  const args = ["--dir", thread.worktreePath];
6052
6359
  if (sessionId) args.push("--session", sessionId);
6053
6360
  return {
6054
- file: "opencode",
6361
+ file: resolveAgentExecutable("opencode"),
6055
6362
  args,
6056
6363
  cwd: thread.worktreePath,
6057
6364
  env: {
@@ -6062,7 +6369,7 @@ var init_opencode = __esm({
6062
6369
  async listLinearIssues(_repoPath) {
6063
6370
  const prompt = "List my assigned Linear issues as JSON array only: id, identifier, title, url, labels.";
6064
6371
  const { stdout, exitCode } = await run(
6065
- "opencode",
6372
+ resolveAgentExecutable("opencode"),
6066
6373
  ["run", prompt, "--format", "json"],
6067
6374
  {
6068
6375
  reject: false,
@@ -6295,9 +6602,30 @@ function getAgentSetupInfo(agent) {
6295
6602
  function listAgentSetupInfo() {
6296
6603
  return Object.values(SETUP);
6297
6604
  }
6298
- async function openInSystemTerminal(command) {
6299
- ensureAgentPath();
6605
+ function resolveLoginCommand(agent) {
6606
+ const info = getAgentSetupInfo(agent);
6607
+ if (!info.loginCommand) return null;
6608
+ if (!CLI_LOGIN_AGENTS.has(agent)) return info.loginCommand;
6609
+ const exe = resolveAgentExecutable(agent);
6610
+ return info.loginCommand.replace(/^\S+/, exe);
6611
+ }
6612
+ async function prepareTerminalCommand(command) {
6613
+ enrichPathWithNpmGlobalBin();
6300
6614
  const trimmed = command.trim();
6615
+ if (!trimmed) return trimmed;
6616
+ if (/^(export\s+PATH=|PATH=)/.test(trimmed)) return trimmed;
6617
+ const match = /^(\S+)(\s[\s\S]*)?$/.exec(trimmed);
6618
+ const bin = match?.[1];
6619
+ let whichPath = null;
6620
+ if (bin && !bin.includes("/") && !bin.includes("\\")) {
6621
+ const which = await run("which", [bin], { reject: false, timeoutMs: 3e3 });
6622
+ whichPath = which.exitCode === 0 ? which.stdout.trim() : null;
6623
+ }
6624
+ const resolved = resolveCommandBinarySync(trimmed, whichPath);
6625
+ return withExportedPath(resolved, process.env.PATH ?? "");
6626
+ }
6627
+ async function openInSystemTerminal(command) {
6628
+ const trimmed = (await prepareTerminalCommand(command)).trim();
6301
6629
  if (!trimmed) throw new Error("Command is empty");
6302
6630
  if (process.platform === "darwin") {
6303
6631
  const escaped = trimmed.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
@@ -6338,6 +6666,11 @@ async function openInSystemTerminal(command) {
6338
6666
  `No terminal found to run: ${trimmed}. Install gnome-terminal (or similar), or run the command manually.`
6339
6667
  );
6340
6668
  }
6669
+ async function whichCli(bin) {
6670
+ const which = await run("which", [bin], { reject: false, timeoutMs: 3e3 });
6671
+ if (which.exitCode !== 0) return null;
6672
+ return which.stdout.trim().split(/\r?\n/).find(Boolean) ?? null;
6673
+ }
6341
6674
  async function installAgent(agent) {
6342
6675
  const info = getAgentSetupInfo(agent);
6343
6676
  if (info.kind === "bundled-sdk" || info.kind === "api-key") {
@@ -6358,7 +6691,18 @@ async function installAgent(agent) {
6358
6691
  message: `Opened Terminal to run: ${info.installCommand}`
6359
6692
  };
6360
6693
  }
6361
- ensureAgentPath();
6694
+ enrichPathWithNpmGlobalBin();
6695
+ const cliBin = agent === "cursor" ? null : CLI_BIN[agent];
6696
+ if (cliBin) {
6697
+ const existing = await whichCli(cliBin);
6698
+ if (existing) {
6699
+ return {
6700
+ ok: true,
6701
+ command: existing,
6702
+ message: isConductorBundledCli(existing) ? `Using Conductor\u2019s ${cliBin} at ${existing}. No extra install \u2014 Log in only if auth is missing.` : `Already on PATH: ${existing}`
6703
+ };
6704
+ }
6705
+ }
6362
6706
  const result = await run("npm", ["install", "-g", info.npmPackage], { reject: false });
6363
6707
  const ok = result.exitCode === 0;
6364
6708
  if (!ok) {
@@ -6384,36 +6728,44 @@ async function installAgent(agent) {
6384
6728
  };
6385
6729
  }
6386
6730
  }
6731
+ enrichPathWithNpmGlobalBin();
6732
+ let binPath = null;
6733
+ if (cliBin) {
6734
+ binPath = await whichCli(cliBin);
6735
+ }
6387
6736
  return {
6388
6737
  ok: true,
6389
6738
  command: info.installCommand,
6390
6739
  exitCode: 0,
6391
6740
  stdout: result.stdout,
6392
6741
  stderr: result.stderr,
6393
- message: `Installed ${info.npmPackage}`
6742
+ 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)`
6394
6743
  };
6395
6744
  }
6396
6745
  async function loginAgent(agent) {
6397
6746
  const info = getAgentSetupInfo(agent);
6398
- if (!info.loginCommand) {
6747
+ const loginCommand = resolveLoginCommand(agent);
6748
+ if (!loginCommand) {
6399
6749
  return {
6400
6750
  ok: true,
6401
6751
  message: info.kind === "bundled-sdk" || info.kind === "api-key" ? info.summary : `No login command for ${agent}`
6402
6752
  };
6403
6753
  }
6404
- await openInSystemTerminal(info.loginCommand);
6754
+ const prepared = await prepareTerminalCommand(loginCommand);
6755
+ await openInSystemTerminal(prepared);
6405
6756
  return {
6406
6757
  ok: true,
6407
6758
  openedTerminal: true,
6408
- command: info.loginCommand,
6409
- message: `Opened Terminal to run: ${info.loginCommand}`
6759
+ command: prepared,
6760
+ message: `Opened Terminal to run: ${prepared}`
6410
6761
  };
6411
6762
  }
6412
- var SETUP;
6763
+ var SETUP, CLI_BIN, CLI_LOGIN_AGENTS;
6413
6764
  var init_install = __esm({
6414
6765
  "src/agents/install.ts"() {
6415
6766
  "use strict";
6416
6767
  init_run();
6768
+ init_app_settings();
6417
6769
  init_path();
6418
6770
  SETUP = {
6419
6771
  claude: {
@@ -6462,6 +6814,18 @@ var init_install = __esm({
6462
6814
  npmPackage: "@brightsy/cli"
6463
6815
  }
6464
6816
  };
6817
+ CLI_BIN = {
6818
+ claude: "claude",
6819
+ codex: "codex",
6820
+ opencode: "opencode",
6821
+ brightsy: "brightsy"
6822
+ };
6823
+ CLI_LOGIN_AGENTS = /* @__PURE__ */ new Set([
6824
+ "claude",
6825
+ "codex",
6826
+ "opencode",
6827
+ "brightsy"
6828
+ ]);
6465
6829
  }
6466
6830
  });
6467
6831
 
@@ -6477,14 +6841,17 @@ __export(agents_exports, {
6477
6841
  claudeAdapter: () => claudeAdapter,
6478
6842
  codexAdapter: () => codexAdapter,
6479
6843
  coerceOrchestratorAgent: () => coerceOrchestratorAgent,
6844
+ conductorBundledBinDir: () => conductorBundledBinDir,
6480
6845
  cursorAdapter: () => cursorAdapter,
6481
6846
  cursorSdkMessageToEvents: () => cursorSdkMessageToEvents,
6482
6847
  decodeBrightsyTarget: () => decodeBrightsyTarget,
6483
6848
  encodeBrightsyTarget: () => encodeBrightsyTarget,
6849
+ enrichPathWithNpmGlobalBin: () => enrichPathWithNpmGlobalBin,
6484
6850
  ensureAgentPath: () => ensureAgentPath,
6485
6851
  getAdapter: () => getAdapter,
6486
6852
  getAgentSetupInfo: () => getAgentSetupInfo,
6487
6853
  installAgent: () => installAgent,
6854
+ isConductorBundledCli: () => isConductorBundledCli,
6488
6855
  isCursorAutoModel: () => isCursorAutoModel,
6489
6856
  isOrchestratorCapableAgent: () => isOrchestratorCapableAgent,
6490
6857
  isSessionQuotaLimit: () => isSessionQuotaLimit,
@@ -6500,8 +6867,13 @@ __export(agents_exports, {
6500
6867
  parseCursorRunnerLine: () => parseCursorRunnerLine,
6501
6868
  parseSessionQuotaResetAt: () => parseSessionQuotaResetAt,
6502
6869
  permissionMode: () => permissionMode,
6870
+ posixShellSingleQuote: () => posixShellSingleQuote,
6871
+ prepareTerminalCommand: () => prepareTerminalCommand,
6872
+ resolveCommandBinarySync: () => resolveCommandBinarySync,
6503
6873
  resolveCursorModelId: () => resolveCursorModelId,
6504
- resolveQuotaFallbackAgent: () => resolveQuotaFallbackAgent
6874
+ resolveLoginCommand: () => resolveLoginCommand,
6875
+ resolveQuotaFallbackAgent: () => resolveQuotaFallbackAgent,
6876
+ withExportedPath: () => withExportedPath
6505
6877
  });
6506
6878
  function getAdapter(kind) {
6507
6879
  return adapters[kind];
@@ -6559,31 +6931,31 @@ function removedWorkspacesFile() {
6559
6931
  }
6560
6932
  function readAll() {
6561
6933
  const path = workspacesFile();
6562
- if (!(0, import_node_fs16.existsSync)(path)) return [];
6934
+ if (!(0, import_node_fs19.existsSync)(path)) return [];
6563
6935
  try {
6564
- const raw = JSON.parse((0, import_node_fs16.readFileSync)(path, "utf8"));
6936
+ const raw = JSON.parse((0, import_node_fs19.readFileSync)(path, "utf8"));
6565
6937
  return Array.isArray(raw) ? raw : [];
6566
6938
  } catch {
6567
6939
  return [];
6568
6940
  }
6569
6941
  }
6570
6942
  function writeAll(list) {
6571
- (0, import_node_fs16.mkdirSync)(appDataDir(), { recursive: true });
6572
- (0, import_node_fs16.writeFileSync)(workspacesFile(), JSON.stringify(list, null, 2), "utf8");
6943
+ (0, import_node_fs19.mkdirSync)(appDataDir(), { recursive: true });
6944
+ (0, import_node_fs19.writeFileSync)(workspacesFile(), JSON.stringify(list, null, 2), "utf8");
6573
6945
  }
6574
6946
  function readRemoved() {
6575
6947
  const path = removedWorkspacesFile();
6576
- if (!(0, import_node_fs16.existsSync)(path)) return /* @__PURE__ */ new Set();
6948
+ if (!(0, import_node_fs19.existsSync)(path)) return /* @__PURE__ */ new Set();
6577
6949
  try {
6578
- const raw = JSON.parse((0, import_node_fs16.readFileSync)(path, "utf8"));
6950
+ const raw = JSON.parse((0, import_node_fs19.readFileSync)(path, "utf8"));
6579
6951
  return new Set(Array.isArray(raw) ? raw.filter((p) => typeof p === "string") : []);
6580
6952
  } catch {
6581
6953
  return /* @__PURE__ */ new Set();
6582
6954
  }
6583
6955
  }
6584
6956
  function writeRemoved(paths) {
6585
- (0, import_node_fs16.mkdirSync)(appDataDir(), { recursive: true });
6586
- (0, import_node_fs16.writeFileSync)(removedWorkspacesFile(), JSON.stringify([...paths].sort(), null, 2), "utf8");
6957
+ (0, import_node_fs19.mkdirSync)(appDataDir(), { recursive: true });
6958
+ (0, import_node_fs19.writeFileSync)(removedWorkspacesFile(), JSON.stringify([...paths].sort(), null, 2), "utf8");
6587
6959
  }
6588
6960
  function rememberRemoved(repoPath) {
6589
6961
  const next = readRemoved();
@@ -6606,7 +6978,7 @@ function listWorkspaces() {
6606
6978
  async function addWorkspace(repoPath) {
6607
6979
  const root = await resolveRepoRoot(repoPath);
6608
6980
  if (!root || root === "/") throw new Error(`Invalid repo path: ${repoPath}`);
6609
- if (!(0, import_node_fs16.existsSync)(root)) throw new Error(`Repo not found: ${root}`);
6981
+ if (!(0, import_node_fs19.existsSync)(root)) throw new Error(`Repo not found: ${root}`);
6610
6982
  forgetRemoved(root);
6611
6983
  await ensureGhPreferOrigin(root);
6612
6984
  const current = readAll();
@@ -6636,7 +7008,7 @@ function syncWorkspacesFromThreads(repoPaths) {
6636
7008
  if (!path || path === "/" || isGlobalRepoPath(path) || byPath.has(path) || removed.has(path)) {
6637
7009
  continue;
6638
7010
  }
6639
- if (!(0, import_node_fs16.existsSync)(path)) continue;
7011
+ if (!(0, import_node_fs19.existsSync)(path)) continue;
6640
7012
  const ws = {
6641
7013
  path,
6642
7014
  name: (0, import_node_path16.basename)(path),
@@ -6649,11 +7021,11 @@ function syncWorkspacesFromThreads(repoPaths) {
6649
7021
  if (dirty) writeAll(next);
6650
7022
  return next.sort((a, b) => a.name.localeCompare(b.name));
6651
7023
  }
6652
- var import_node_fs16, import_node_path16;
7024
+ var import_node_fs19, import_node_path16;
6653
7025
  var init_workspaces = __esm({
6654
7026
  "src/store/workspaces.ts"() {
6655
7027
  "use strict";
6656
- import_node_fs16 = require("fs");
7028
+ import_node_fs19 = require("fs");
6657
7029
  import_node_path16 = require("path");
6658
7030
  init_paths();
6659
7031
  init_global_workspace();
@@ -6732,17 +7104,17 @@ __export(plan_file_exports, {
6732
7104
  });
6733
7105
  function ensureAttachmentsGitignore2(worktreePath) {
6734
7106
  const gitignoreAbs = (0, import_node_path24.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
6735
- if ((0, import_node_fs26.existsSync)(gitignoreAbs)) return;
6736
- (0, import_node_fs26.mkdirSync)((0, import_node_path24.dirname)(gitignoreAbs), { recursive: true });
6737
- (0, import_node_fs26.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
7107
+ if ((0, import_node_fs29.existsSync)(gitignoreAbs)) return;
7108
+ (0, import_node_fs29.mkdirSync)((0, import_node_path24.dirname)(gitignoreAbs), { recursive: true });
7109
+ (0, import_node_fs29.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
6738
7110
  }
6739
7111
  function planFileAbs(worktreePath) {
6740
7112
  return (0, import_node_path24.join)(worktreePath, PLAN_FILE_REL);
6741
7113
  }
6742
7114
  function readTextIfPresent2(abs) {
6743
- if (!(0, import_node_fs26.existsSync)(abs)) return null;
7115
+ if (!(0, import_node_fs29.existsSync)(abs)) return null;
6744
7116
  try {
6745
- const content = (0, import_node_fs26.readFileSync)(abs, "utf8");
7117
+ const content = (0, import_node_fs29.readFileSync)(abs, "utf8");
6746
7118
  return content.trim() ? content : null;
6747
7119
  } catch {
6748
7120
  return null;
@@ -6754,16 +7126,16 @@ function readPlanFile(worktreePath) {
6754
7126
  function writePlanFile(worktreePath, content) {
6755
7127
  ensureAttachmentsGitignore2(worktreePath);
6756
7128
  const abs = planFileAbs(worktreePath);
6757
- (0, import_node_fs26.mkdirSync)((0, import_node_path24.dirname)(abs), { recursive: true });
7129
+ (0, import_node_fs29.mkdirSync)((0, import_node_path24.dirname)(abs), { recursive: true });
6758
7130
  const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
6759
- (0, import_node_fs26.writeFileSync)(abs, body, "utf8");
7131
+ (0, import_node_fs29.writeFileSync)(abs, body, "utf8");
6760
7132
  return PLAN_FILE_REL;
6761
7133
  }
6762
- var import_node_fs26, import_node_path24;
7134
+ var import_node_fs29, import_node_path24;
6763
7135
  var init_plan_file = __esm({
6764
7136
  "src/plan/plan-file.ts"() {
6765
7137
  "use strict";
6766
- import_node_fs26 = require("fs");
7138
+ import_node_fs29 = require("fs");
6767
7139
  import_node_path24 = require("path");
6768
7140
  init_workspace_scratch();
6769
7141
  init_plan_present();
@@ -6780,9 +7152,9 @@ function recoverFinishedCursorRun(opts) {
6780
7152
  const agentId = opts.agentId.trim();
6781
7153
  if (!agentId) return null;
6782
7154
  const runsPath = (0, import_node_path25.join)(appDataDir(), "cursor-sdk-store", "runs.ndjson");
6783
- if (!(0, import_node_fs27.existsSync)(runsPath)) return null;
7155
+ if (!(0, import_node_fs30.existsSync)(runsPath)) return null;
6784
7156
  try {
6785
- const lines = (0, import_node_fs27.readFileSync)(runsPath, "utf8").split("\n");
7157
+ const lines = (0, import_node_fs30.readFileSync)(runsPath, "utf8").split("\n");
6786
7158
  let best = null;
6787
7159
  for (const line of lines) {
6788
7160
  const trimmed = line.trim();
@@ -6808,11 +7180,11 @@ function recoverFinishedCursorRun(opts) {
6808
7180
  return null;
6809
7181
  }
6810
7182
  }
6811
- var import_node_fs27, import_node_path25;
7183
+ var import_node_fs30, import_node_path25;
6812
7184
  var init_cursor_recover = __esm({
6813
7185
  "src/agents/cursor-recover.ts"() {
6814
7186
  "use strict";
6815
- import_node_fs27 = require("fs");
7187
+ import_node_fs30 = require("fs");
6816
7188
  import_node_path25 = require("path");
6817
7189
  init_paths();
6818
7190
  }
@@ -6826,7 +7198,7 @@ var import_node_path26 = require("path");
6826
7198
 
6827
7199
  // src/orchestrator/orchestrator.ts
6828
7200
  var import_node_events = require("events");
6829
- var import_node_fs28 = require("fs");
7201
+ var import_node_fs31 = require("fs");
6830
7202
  init_error_detail();
6831
7203
 
6832
7204
  // src/agents/spawn.ts
@@ -7216,7 +7588,7 @@ function shouldAutoArchiveOnPrMerge(opts) {
7216
7588
  }
7217
7589
 
7218
7590
  // src/hook/conductor.ts
7219
- var import_node_fs13 = require("fs");
7591
+ var import_node_fs16 = require("fs");
7220
7592
  var import_node_net = require("net");
7221
7593
  var import_node_path13 = require("path");
7222
7594
  var import_execa3 = require("execa");
@@ -7229,8 +7601,8 @@ function matchSimpleGlob(pattern, name) {
7229
7601
  }
7230
7602
  function readWorktreeInclude(repoPath) {
7231
7603
  const path = (0, import_node_path13.join)(repoPath, ".worktreeinclude");
7232
- if (!(0, import_node_fs13.existsSync)(path)) return [];
7233
- return (0, import_node_fs13.readFileSync)(path, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
7604
+ if (!(0, import_node_fs16.existsSync)(path)) return [];
7605
+ return (0, import_node_fs16.readFileSync)(path, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
7234
7606
  }
7235
7607
  function resolveFilesToCopy(repoPath) {
7236
7608
  const fromInclude = readWorktreeInclude(repoPath);
@@ -7240,7 +7612,7 @@ function resolveFilesToCopy(repoPath) {
7240
7612
  if (settings?.fileIncludeGlobs?.length) {
7241
7613
  const matched = [];
7242
7614
  try {
7243
- for (const entry of (0, import_node_fs13.readdirSync)(repoPath, { withFileTypes: true })) {
7615
+ for (const entry of (0, import_node_fs16.readdirSync)(repoPath, { withFileTypes: true })) {
7244
7616
  if (!entry.isFile()) continue;
7245
7617
  for (const glob of settings.fileIncludeGlobs) {
7246
7618
  if (matchSimpleGlob(glob, entry.name) || matchSimpleGlob((0, import_node_path13.basename)(glob), entry.name)) {
@@ -7255,7 +7627,7 @@ function resolveFilesToCopy(repoPath) {
7255
7627
  }
7256
7628
  const defaults = [];
7257
7629
  try {
7258
- for (const entry of (0, import_node_fs13.readdirSync)(repoPath, { withFileTypes: true })) {
7630
+ for (const entry of (0, import_node_fs16.readdirSync)(repoPath, { withFileTypes: true })) {
7259
7631
  if (entry.isFile() && entry.name.startsWith(".env")) {
7260
7632
  defaults.push(entry.name);
7261
7633
  }
@@ -7270,10 +7642,10 @@ function copyConfiguredFiles(repoPath, worktreePath) {
7270
7642
  const copied = [];
7271
7643
  for (const rel of patterns) {
7272
7644
  const src = (0, import_node_path13.join)(repoPath, rel);
7273
- if (!(0, import_node_fs13.existsSync)(src)) continue;
7645
+ if (!(0, import_node_fs16.existsSync)(src)) continue;
7274
7646
  const dest = (0, import_node_path13.join)(worktreePath, rel);
7275
- (0, import_node_fs13.mkdirSync)((0, import_node_path13.dirname)(dest), { recursive: true });
7276
- (0, import_node_fs13.copyFileSync)(src, dest);
7647
+ (0, import_node_fs16.mkdirSync)((0, import_node_path13.dirname)(dest), { recursive: true });
7648
+ (0, import_node_fs16.copyFileSync)(src, dest);
7277
7649
  copied.push(rel);
7278
7650
  }
7279
7651
  return copied;
@@ -7526,15 +7898,15 @@ async function startDevServer(repoPath, worktreePath, onLine, opts) {
7526
7898
  }
7527
7899
 
7528
7900
  // src/hook/cursor-worktrees.ts
7529
- var import_node_fs14 = require("fs");
7901
+ var import_node_fs17 = require("fs");
7530
7902
  var import_node_path14 = require("path");
7531
7903
  var import_execa4 = require("execa");
7532
7904
  var import_node_readline3 = require("readline");
7533
7905
  function loadCursorWorktreesJson(rootPath) {
7534
7906
  const path = (0, import_node_path14.join)(rootPath, ".cursor", "worktrees.json");
7535
- if (!(0, import_node_fs14.existsSync)(path)) return null;
7907
+ if (!(0, import_node_fs17.existsSync)(path)) return null;
7536
7908
  try {
7537
- return JSON.parse((0, import_node_fs14.readFileSync)(path, "utf8"));
7909
+ return JSON.parse((0, import_node_fs17.readFileSync)(path, "utf8"));
7538
7910
  } catch {
7539
7911
  return null;
7540
7912
  }
@@ -7592,7 +7964,7 @@ async function runCursorWorktreeSetup(repoPath, worktreePath, onLine) {
7592
7964
  }
7593
7965
 
7594
7966
  // src/git/orphan-cleanup.ts
7595
- var import_node_fs15 = require("fs");
7967
+ var import_node_fs18 = require("fs");
7596
7968
  var import_node_path15 = require("path");
7597
7969
  init_worktree();
7598
7970
  init_thread_store();
@@ -7608,9 +7980,9 @@ async function findOrphanWorktrees(repoPaths) {
7608
7980
  repoPaths?.length ? repoPaths : threads.map((t) => t.repoPath).filter(Boolean)
7609
7981
  );
7610
7982
  const homeRoot = sideboardWorkspacesDir();
7611
- if ((0, import_node_fs15.existsSync)(homeRoot)) {
7983
+ if ((0, import_node_fs18.existsSync)(homeRoot)) {
7612
7984
  try {
7613
- for (const entry of (0, import_node_fs15.readdirSync)(homeRoot, { withFileTypes: true })) {
7985
+ for (const entry of (0, import_node_fs18.readdirSync)(homeRoot, { withFileTypes: true })) {
7614
7986
  if (!entry.isDirectory()) continue;
7615
7987
  void entry;
7616
7988
  }
@@ -7620,7 +7992,7 @@ async function findOrphanWorktrees(repoPaths) {
7620
7992
  const orphans = [];
7621
7993
  const seen = /* @__PURE__ */ new Set();
7622
7994
  for (const repoPath of repos) {
7623
- if (!repoPath || !(0, import_node_fs15.existsSync)(repoPath)) continue;
7995
+ if (!repoPath || !(0, import_node_fs18.existsSync)(repoPath)) continue;
7624
7996
  try {
7625
7997
  const wts = await listWorktrees(repoPath);
7626
7998
  for (const wt of wts) {
@@ -7631,7 +8003,7 @@ async function findOrphanWorktrees(repoPaths) {
7631
8003
  seen.add(path);
7632
8004
  let mtimeMs = 0;
7633
8005
  try {
7634
- mtimeMs = (0, import_node_fs15.statSync)(path).mtimeMs;
8006
+ mtimeMs = (0, import_node_fs18.statSync)(path).mtimeMs;
7635
8007
  } catch {
7636
8008
  mtimeMs = 0;
7637
8009
  }
@@ -7641,16 +8013,16 @@ async function findOrphanWorktrees(repoPaths) {
7641
8013
  }
7642
8014
  try {
7643
8015
  const root = worktreesRoot(repoPath);
7644
- if ((0, import_node_fs15.existsSync)(root)) {
7645
- for (const entry of (0, import_node_fs15.readdirSync)(root, { withFileTypes: true })) {
8016
+ if ((0, import_node_fs18.existsSync)(root)) {
8017
+ for (const entry of (0, import_node_fs18.readdirSync)(root, { withFileTypes: true })) {
7646
8018
  if (!entry.isDirectory()) continue;
7647
8019
  const path = (0, import_node_path15.join)(root, entry.name).replace(/\/$/, "");
7648
8020
  if (known.has(path) || seen.has(path)) continue;
7649
- if (!(0, import_node_fs15.existsSync)((0, import_node_path15.join)(path, ".git"))) continue;
8021
+ if (!(0, import_node_fs18.existsSync)((0, import_node_path15.join)(path, ".git"))) continue;
7650
8022
  seen.add(path);
7651
8023
  let mtimeMs = 0;
7652
8024
  try {
7653
- mtimeMs = (0, import_node_fs15.statSync)(path).mtimeMs;
8025
+ mtimeMs = (0, import_node_fs18.statSync)(path).mtimeMs;
7654
8026
  } catch {
7655
8027
  mtimeMs = Date.now();
7656
8028
  }
@@ -7787,7 +8159,7 @@ async function applyThreadIntoMain(thread, opts) {
7787
8159
  }
7788
8160
 
7789
8161
  // src/git/clone-repo.ts
7790
- var import_node_fs17 = require("fs");
8162
+ var import_node_fs20 = require("fs");
7791
8163
  var import_node_path17 = require("path");
7792
8164
  var import_execa6 = require("execa");
7793
8165
  init_paths();
@@ -7803,7 +8175,7 @@ async function cloneRepoIntoSideboard(opts) {
7803
8175
  }
7804
8176
  name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
7805
8177
  const dest = (0, import_node_path17.join)(sideboardReposDir(), name);
7806
- if ((0, import_node_fs17.existsSync)(dest)) {
8178
+ if ((0, import_node_fs20.existsSync)(dest)) {
7807
8179
  const repoPath2 = await resolveRepoRoot(dest);
7808
8180
  const workspace2 = await ensureWorkspace(repoPath2);
7809
8181
  return { repoPath: repoPath2, workspace: workspace2 };
@@ -7823,13 +8195,14 @@ async function cloneRepoIntoSideboard(opts) {
7823
8195
  init_thread_store();
7824
8196
 
7825
8197
  // src/threads/create.ts
7826
- var import_node_fs18 = require("fs");
8198
+ var import_node_fs21 = require("fs");
7827
8199
 
7828
8200
  // src/detect/detect.ts
7829
8201
  init_agents();
7830
8202
  var REQUIRE_AGENT_TIMEOUT_MS = 8e3;
7831
8203
  async function requireAgent(agent, opts) {
7832
8204
  ensureAgentPath();
8205
+ enrichPathWithNpmGlobalBin();
7833
8206
  const status = await Promise.race([
7834
8207
  getAdapter(agent).detect(),
7835
8208
  new Promise((_, reject) => {
@@ -7867,7 +8240,7 @@ async function createThread(input, _onSetupLine) {
7867
8240
  });
7868
8241
  await requireAgent(resolved.agent);
7869
8242
  const repoPath = await resolveRepoRoot(input.repoPath);
7870
- if (!(0, import_node_fs18.existsSync)(repoPath)) {
8243
+ if (!(0, import_node_fs21.existsSync)(repoPath)) {
7871
8244
  throw new Error(`Repo not found: ${repoPath}`);
7872
8245
  }
7873
8246
  let sourceRef = input.sourceRef;
@@ -8330,7 +8703,7 @@ function forkChatTab(input) {
8330
8703
 
8331
8704
  // src/review/request-review.ts
8332
8705
  var import_node_crypto3 = require("crypto");
8333
- var import_node_fs19 = require("fs");
8706
+ var import_node_fs22 = require("fs");
8334
8707
  var import_node_path18 = require("path");
8335
8708
  init_global_workspace();
8336
8709
  init_thread_store();
@@ -8475,9 +8848,9 @@ function shouldRefreshReviewRequestTemplate(content) {
8475
8848
  return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
8476
8849
  }
8477
8850
  function readTextIfPresent(abs) {
8478
- if (!(0, import_node_fs19.existsSync)(abs)) return null;
8851
+ if (!(0, import_node_fs22.existsSync)(abs)) return null;
8479
8852
  try {
8480
- const content = (0, import_node_fs19.readFileSync)(abs, "utf8");
8853
+ const content = (0, import_node_fs22.readFileSync)(abs, "utf8");
8481
8854
  return content.trim() ? content : null;
8482
8855
  } catch {
8483
8856
  return null;
@@ -8485,9 +8858,9 @@ function readTextIfPresent(abs) {
8485
8858
  }
8486
8859
  function ensureAttachmentsGitignore(worktreePath) {
8487
8860
  const gitignoreAbs = (0, import_node_path18.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
8488
- if ((0, import_node_fs19.existsSync)(gitignoreAbs)) return;
8489
- (0, import_node_fs19.mkdirSync)((0, import_node_path18.dirname)(gitignoreAbs), { recursive: true });
8490
- (0, import_node_fs19.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
8861
+ if ((0, import_node_fs22.existsSync)(gitignoreAbs)) return;
8862
+ (0, import_node_fs22.mkdirSync)((0, import_node_path18.dirname)(gitignoreAbs), { recursive: true });
8863
+ (0, import_node_fs22.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
8491
8864
  }
8492
8865
  function resolveReviewGuidelines(worktreePath) {
8493
8866
  const repoAbs = (0, import_node_path18.join)(worktreePath, REPO_REVIEW_PATH);
@@ -8521,8 +8894,8 @@ function resolveReviewGuidelines(worktreePath) {
8521
8894
  };
8522
8895
  }
8523
8896
  ensureAttachmentsGitignore(worktreePath);
8524
- (0, import_node_fs19.mkdirSync)((0, import_node_path18.dirname)(localAbs), { recursive: true });
8525
- (0, import_node_fs19.writeFileSync)(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
8897
+ (0, import_node_fs22.mkdirSync)((0, import_node_path18.dirname)(localAbs), { recursive: true });
8898
+ (0, import_node_fs22.writeFileSync)(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
8526
8899
  return {
8527
8900
  path: REVIEW_REQUEST_PATH,
8528
8901
  name: REVIEW_REQUEST_NAME,
@@ -8721,8 +9094,8 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
8721
9094
  }
8722
9095
 
8723
9096
  // src/threads/adopt.ts
8724
- var import_node_child_process = require("child_process");
8725
- var import_node_fs20 = require("fs");
9097
+ var import_node_child_process2 = require("child_process");
9098
+ var import_node_fs23 = require("fs");
8726
9099
  var import_node_os8 = require("os");
8727
9100
  var import_node_path19 = require("path");
8728
9101
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
@@ -8747,21 +9120,21 @@ function mapAgentType(raw) {
8747
9120
  return null;
8748
9121
  }
8749
9122
  function resolveConductorCursorAgentId(workspacePath) {
8750
- if (!workspacePath || !(0, import_node_fs20.existsSync)(CURSOR_SDK_STORE)) return null;
9123
+ if (!workspacePath || !(0, import_node_fs23.existsSync)(CURSOR_SDK_STORE)) return null;
8751
9124
  const normalized = workspacePath.replace(/\/$/, "");
8752
9125
  let best = null;
8753
9126
  let hashes;
8754
9127
  try {
8755
- hashes = (0, import_node_fs20.readdirSync)(CURSOR_SDK_STORE);
9128
+ hashes = (0, import_node_fs23.readdirSync)(CURSOR_SDK_STORE);
8756
9129
  } catch {
8757
9130
  return null;
8758
9131
  }
8759
9132
  for (const hash of hashes) {
8760
9133
  const agentsFile = (0, import_node_path19.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
8761
- if (!(0, import_node_fs20.existsSync)(agentsFile)) continue;
9134
+ if (!(0, import_node_fs23.existsSync)(agentsFile)) continue;
8762
9135
  let text;
8763
9136
  try {
8764
- text = (0, import_node_fs20.readFileSync)(agentsFile, "utf8");
9137
+ text = (0, import_node_fs23.readFileSync)(agentsFile, "utf8");
8765
9138
  } catch {
8766
9139
  continue;
8767
9140
  }
@@ -8785,7 +9158,7 @@ function resolveConductorCursorAgentId(workspacePath) {
8785
9158
  return best?.agentId ?? null;
8786
9159
  }
8787
9160
  async function adoptThread(input) {
8788
- if (!(0, import_node_fs20.existsSync)(input.worktreePath)) {
9161
+ if (!(0, import_node_fs23.existsSync)(input.worktreePath)) {
8789
9162
  throw new Error(`Worktree not found: ${input.worktreePath}`);
8790
9163
  }
8791
9164
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -8809,18 +9182,18 @@ async function adoptThread(input) {
8809
9182
  return thread;
8810
9183
  }
8811
9184
  function listConductorWorkspaces() {
8812
- if (!(0, import_node_fs20.existsSync)(CONDUCTOR_DB)) {
9185
+ if (!(0, import_node_fs23.existsSync)(CONDUCTOR_DB)) {
8813
9186
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
8814
9187
  }
8815
- const tmp = (0, import_node_fs20.mkdtempSync)((0, import_node_path19.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
9188
+ const tmp = (0, import_node_fs23.mkdtempSync)((0, import_node_path19.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
8816
9189
  const snapshot = (0, import_node_path19.join)(tmp, "conductor.db");
8817
9190
  try {
8818
- (0, import_node_fs20.copyFileSync)(CONDUCTOR_DB, snapshot);
9191
+ (0, import_node_fs23.copyFileSync)(CONDUCTOR_DB, snapshot);
8819
9192
  for (const suffix of ["-wal", "-shm"]) {
8820
9193
  const src = `${CONDUCTOR_DB}${suffix}`;
8821
- if ((0, import_node_fs20.existsSync)(src)) {
9194
+ if ((0, import_node_fs23.existsSync)(src)) {
8822
9195
  try {
8823
- (0, import_node_fs20.copyFileSync)(src, `${snapshot}${suffix}`);
9196
+ (0, import_node_fs23.copyFileSync)(src, `${snapshot}${suffix}`);
8824
9197
  } catch {
8825
9198
  }
8826
9199
  }
@@ -8896,22 +9269,22 @@ function listConductorWorkspaces() {
8896
9269
  db.close();
8897
9270
  }
8898
9271
  } finally {
8899
- (0, import_node_fs20.rmSync)(tmp, { recursive: true, force: true });
9272
+ (0, import_node_fs23.rmSync)(tmp, { recursive: true, force: true });
8900
9273
  }
8901
9274
  }
8902
9275
  function importConductorWorkspace(workspaceId) {
8903
- if (!(0, import_node_fs20.existsSync)(CONDUCTOR_DB)) {
9276
+ if (!(0, import_node_fs23.existsSync)(CONDUCTOR_DB)) {
8904
9277
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
8905
9278
  }
8906
- const tmp = (0, import_node_fs20.mkdtempSync)((0, import_node_path19.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
9279
+ const tmp = (0, import_node_fs23.mkdtempSync)((0, import_node_path19.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
8907
9280
  const snapshot = (0, import_node_path19.join)(tmp, "conductor.db");
8908
9281
  try {
8909
- (0, import_node_fs20.copyFileSync)(CONDUCTOR_DB, snapshot);
9282
+ (0, import_node_fs23.copyFileSync)(CONDUCTOR_DB, snapshot);
8910
9283
  for (const suffix of ["-wal", "-shm"]) {
8911
9284
  const src = `${CONDUCTOR_DB}${suffix}`;
8912
- if ((0, import_node_fs20.existsSync)(src)) {
9285
+ if ((0, import_node_fs23.existsSync)(src)) {
8913
9286
  try {
8914
- (0, import_node_fs20.copyFileSync)(src, `${snapshot}${suffix}`);
9287
+ (0, import_node_fs23.copyFileSync)(src, `${snapshot}${suffix}`);
8915
9288
  } catch {
8916
9289
  }
8917
9290
  }
@@ -8929,7 +9302,7 @@ function importConductorWorkspace(workspaceId) {
8929
9302
  ).get(workspaceId);
8930
9303
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
8931
9304
  const worktreePath = String(row.workspacePath);
8932
- if (!(0, import_node_fs20.existsSync)(worktreePath)) {
9305
+ if (!(0, import_node_fs23.existsSync)(worktreePath)) {
8933
9306
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
8934
9307
  }
8935
9308
  let sessionId = null;
@@ -8969,7 +9342,7 @@ function importConductorWorkspace(workspaceId) {
8969
9342
  }
8970
9343
  let repoPath = worktreePath;
8971
9344
  try {
8972
- repoPath = (0, import_node_child_process.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
9345
+ repoPath = (0, import_node_child_process2.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
8973
9346
  cwd: worktreePath,
8974
9347
  encoding: "utf8"
8975
9348
  }).trim();
@@ -8992,7 +9365,7 @@ function importConductorWorkspace(workspaceId) {
8992
9365
  db.close();
8993
9366
  }
8994
9367
  } finally {
8995
- (0, import_node_fs20.rmSync)(tmp, { recursive: true, force: true });
9368
+ (0, import_node_fs23.rmSync)(tmp, { recursive: true, force: true });
8996
9369
  }
8997
9370
  }
8998
9371
  async function importConductorWorkspaceAsync(workspaceId) {
@@ -9000,7 +9373,7 @@ async function importConductorWorkspaceAsync(workspaceId) {
9000
9373
  }
9001
9374
 
9002
9375
  // src/threads/stack-layers.ts
9003
- var import_node_fs21 = require("fs");
9376
+ var import_node_fs24 = require("fs");
9004
9377
  init_run();
9005
9378
  init_stack();
9006
9379
  init_worktree();
@@ -9068,7 +9441,7 @@ async function openStackLayer(input, _onSetupLine) {
9068
9441
  let createdWorktree = false;
9069
9442
  const trees = await listWorktrees(repoPath);
9070
9443
  const checkedOut = trees.find((w) => w.branch === branchName);
9071
- if (checkedOut?.path && (0, import_node_fs21.existsSync)(checkedOut.path)) {
9444
+ if (checkedOut?.path && (0, import_node_fs24.existsSync)(checkedOut.path)) {
9072
9445
  if (input.reuseExistingWorktree !== false) {
9073
9446
  worktreePath = checkedOut.path;
9074
9447
  } else {
@@ -9210,7 +9583,7 @@ async function initStackFromThread(input, onSetupLine) {
9210
9583
  async function createPrStack(input, onSetupLine) {
9211
9584
  await requireAgent(input.agent);
9212
9585
  const repoPath = await resolveRepoRoot(input.repoPath);
9213
- if (!(0, import_node_fs21.existsSync)(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
9586
+ if (!(0, import_node_fs24.existsSync)(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
9214
9587
  if (!input.branches.length) throw new Error("At least one branch name required");
9215
9588
  const status = await detectGhStack(repoPath);
9216
9589
  if (!status.available) throw new Error(status.reason);
@@ -9277,7 +9650,7 @@ async function createPrStack(input, onSetupLine) {
9277
9650
  }
9278
9651
  }
9279
9652
  const claimed = new Set(threads.map((t) => t.worktreePath));
9280
- if (!claimed.has(bootstrap.worktreePath) && (0, import_node_fs21.existsSync)(bootstrap.worktreePath)) {
9653
+ if (!claimed.has(bootstrap.worktreePath) && (0, import_node_fs24.existsSync)(bootstrap.worktreePath)) {
9281
9654
  try {
9282
9655
  await removeWorktree(repoPath, bootstrap.worktreePath, {
9283
9656
  deleteBranch: bootstrap.branchName
@@ -9292,12 +9665,12 @@ async function createPrStack(input, onSetupLine) {
9292
9665
  init_worktree();
9293
9666
 
9294
9667
  // src/diff/diff.ts
9295
- var import_node_fs22 = require("fs");
9668
+ var import_node_fs25 = require("fs");
9296
9669
  var import_node_path20 = require("path");
9297
9670
  init_run();
9298
9671
  init_worktree();
9299
9672
  async function inspectGitWorktree(worktreePath) {
9300
- if (!worktreePath || !(0, import_node_fs22.existsSync)(worktreePath)) return "missing_worktree";
9673
+ if (!worktreePath || !(0, import_node_fs25.existsSync)(worktreePath)) return "missing_worktree";
9301
9674
  const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
9302
9675
  reject: false
9303
9676
  });
@@ -9305,7 +9678,7 @@ async function inspectGitWorktree(worktreePath) {
9305
9678
  return "ok";
9306
9679
  }
9307
9680
  async function initializeGitRepository(worktreePath) {
9308
- if (!worktreePath || !(0, import_node_fs22.existsSync)(worktreePath)) {
9681
+ if (!worktreePath || !(0, import_node_fs25.existsSync)(worktreePath)) {
9309
9682
  throw new Error("Worktree not found");
9310
9683
  }
9311
9684
  const status = await inspectGitWorktree(worktreePath);
@@ -9743,7 +10116,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
9743
10116
  assertSafeRelativePath(relativePath);
9744
10117
  const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
9745
10118
  const abs = (0, import_node_path20.join)(worktreePath, relativePath);
9746
- const st = (0, import_node_fs22.statSync)(abs);
10119
+ const st = (0, import_node_fs25.statSync)(abs);
9747
10120
  if (!st.isFile()) {
9748
10121
  throw new Error(`Not a file: ${relativePath}`);
9749
10122
  }
@@ -9752,7 +10125,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
9752
10125
  `File too large to upload (${st.size} bytes; max ${maxBytes})`
9753
10126
  );
9754
10127
  }
9755
- const buf = (0, import_node_fs22.readFileSync)(abs);
10128
+ const buf = (0, import_node_fs25.readFileSync)(abs);
9756
10129
  return {
9757
10130
  path: relativePath,
9758
10131
  contentBase64: buf.toString("base64"),
@@ -9763,11 +10136,11 @@ function readWorktreeFile(worktreePath, relativePath, opts) {
9763
10136
  assertSafeRelativePath(relativePath);
9764
10137
  const maxBytes = opts?.maxBytes ?? 2e5;
9765
10138
  const abs = (0, import_node_path20.join)(worktreePath, relativePath);
9766
- const st = (0, import_node_fs22.statSync)(abs);
10139
+ const st = (0, import_node_fs25.statSync)(abs);
9767
10140
  if (!st.isFile()) {
9768
10141
  throw new Error(`Not a file: ${relativePath}`);
9769
10142
  }
9770
- const buf = (0, import_node_fs22.readFileSync)(abs);
10143
+ const buf = (0, import_node_fs25.readFileSync)(abs);
9771
10144
  if (isImageRelativePath(relativePath)) {
9772
10145
  const maxImageBytes = Math.max(maxBytes, 15e6);
9773
10146
  const truncated2 = buf.length > maxImageBytes;
@@ -9811,8 +10184,8 @@ function assertSafeRelativePath(relativePath) {
9811
10184
  function writeWorktreeFile(worktreePath, relativePath, content) {
9812
10185
  assertSafeRelativePath(relativePath);
9813
10186
  const abs = (0, import_node_path20.join)(worktreePath, relativePath);
9814
- (0, import_node_fs22.mkdirSync)((0, import_node_path20.dirname)(abs), { recursive: true });
9815
- (0, import_node_fs22.writeFileSync)(abs, content, "utf8");
10187
+ (0, import_node_fs25.mkdirSync)((0, import_node_path20.dirname)(abs), { recursive: true });
10188
+ (0, import_node_fs25.writeFileSync)(abs, content, "utf8");
9816
10189
  return { path: relativePath };
9817
10190
  }
9818
10191
  async function getDiffSummary(worktreePath, repoPath, opts) {
@@ -9991,7 +10364,7 @@ async function confirmLand(thread, opts) {
9991
10364
  }
9992
10365
 
9993
10366
  // src/skills/discover.ts
9994
- var import_node_fs23 = require("fs");
10367
+ var import_node_fs26 = require("fs");
9995
10368
  var import_node_os9 = require("os");
9996
10369
  var import_node_path21 = require("path");
9997
10370
  function toCommand(name) {
@@ -10025,7 +10398,7 @@ function parseFrontmatter(content) {
10025
10398
  }
10026
10399
  function readSkill(skillMd, source) {
10027
10400
  try {
10028
- const content = (0, import_node_fs23.readFileSync)(skillMd, "utf8");
10401
+ const content = (0, import_node_fs26.readFileSync)(skillMd, "utf8");
10029
10402
  const { name: fmName, description } = parseFrontmatter(content);
10030
10403
  const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
10031
10404
  const name = fmName || dirName;
@@ -10044,19 +10417,19 @@ function readSkill(skillMd, source) {
10044
10417
  }
10045
10418
  }
10046
10419
  function scanSkillsDir(dir, source, out) {
10047
- if (!(0, import_node_fs23.existsSync)(dir)) return;
10420
+ if (!(0, import_node_fs26.existsSync)(dir)) return;
10048
10421
  let entries;
10049
10422
  try {
10050
- entries = (0, import_node_fs23.readdirSync)(dir);
10423
+ entries = (0, import_node_fs26.readdirSync)(dir);
10051
10424
  } catch {
10052
10425
  return;
10053
10426
  }
10054
10427
  for (const entry of entries) {
10055
10428
  if (entry.startsWith(".")) continue;
10056
10429
  const skillMd = (0, import_node_path21.join)(dir, entry, "SKILL.md");
10057
- if (!(0, import_node_fs23.existsSync)(skillMd)) continue;
10430
+ if (!(0, import_node_fs26.existsSync)(skillMd)) continue;
10058
10431
  try {
10059
- if (!(0, import_node_fs23.statSync)(skillMd).isFile()) continue;
10432
+ if (!(0, import_node_fs26.statSync)(skillMd).isFile()) continue;
10060
10433
  } catch {
10061
10434
  continue;
10062
10435
  }
@@ -10065,12 +10438,12 @@ function scanSkillsDir(dir, source, out) {
10065
10438
  }
10066
10439
  }
10067
10440
  function scanClaudePluginSkills(pluginsRoot, out) {
10068
- if (!(0, import_node_fs23.existsSync)(pluginsRoot)) return;
10441
+ if (!(0, import_node_fs26.existsSync)(pluginsRoot)) return;
10069
10442
  const walk = (dir, depth, lookingForSkillsDir) => {
10070
10443
  if (depth > 7) return;
10071
10444
  let entries;
10072
10445
  try {
10073
- entries = (0, import_node_fs23.readdirSync)(dir);
10446
+ entries = (0, import_node_fs26.readdirSync)(dir);
10074
10447
  } catch {
10075
10448
  return;
10076
10449
  }
@@ -10082,7 +10455,7 @@ function scanClaudePluginSkills(pluginsRoot, out) {
10082
10455
  if (entry === "node_modules" || entry === ".git") continue;
10083
10456
  const full = (0, import_node_path21.join)(dir, entry);
10084
10457
  try {
10085
- if (!(0, import_node_fs23.statSync)(full).isDirectory()) continue;
10458
+ if (!(0, import_node_fs26.statSync)(full).isDirectory()) continue;
10086
10459
  } catch {
10087
10460
  continue;
10088
10461
  }
@@ -10122,7 +10495,7 @@ function discoverSkills(worktreePath) {
10122
10495
  return [...byCommand.values()].sort((a, b) => a.command.localeCompare(b.command));
10123
10496
  }
10124
10497
  function readSkillBody(skillPath, maxChars = 12e3) {
10125
- const raw = (0, import_node_fs23.readFileSync)(skillPath, "utf8");
10498
+ const raw = (0, import_node_fs26.readFileSync)(skillPath, "utf8");
10126
10499
  if (raw.startsWith("---")) {
10127
10500
  const end = raw.indexOf("\n---", 3);
10128
10501
  if (end >= 0) {
@@ -10215,7 +10588,7 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
10215
10588
  }
10216
10589
 
10217
10590
  // src/composer/stage-files.ts
10218
- var import_node_fs24 = require("fs");
10591
+ var import_node_fs27 = require("fs");
10219
10592
  var import_node_path22 = require("path");
10220
10593
  var import_node_crypto5 = require("crypto");
10221
10594
  init_workspace_scratch();
@@ -10253,21 +10626,21 @@ function imageMimeType(filePath) {
10253
10626
  }
10254
10627
  function ensureAttachmentsDir(worktreePath) {
10255
10628
  const dir = (0, import_node_path22.join)(worktreePath, ATTACHMENTS_DIR);
10256
- (0, import_node_fs24.mkdirSync)(dir, { recursive: true });
10629
+ (0, import_node_fs27.mkdirSync)(dir, { recursive: true });
10257
10630
  const gi = (0, import_node_path22.join)(dir, ".gitignore");
10258
- if (!(0, import_node_fs24.existsSync)(gi)) {
10259
- (0, import_node_fs24.writeFileSync)(gi, attachmentsGitignoreBody(), "utf8");
10631
+ if (!(0, import_node_fs27.existsSync)(gi)) {
10632
+ (0, import_node_fs27.writeFileSync)(gi, attachmentsGitignoreBody(), "utf8");
10260
10633
  }
10261
10634
  return dir;
10262
10635
  }
10263
10636
  function uniqueAttachmentName(dir, originalName) {
10264
10637
  const safe = originalName.replace(/[/\\]/g, "_") || "file";
10265
- if (!(0, import_node_fs24.existsSync)((0, import_node_path22.join)(dir, safe))) return safe;
10638
+ if (!(0, import_node_fs27.existsSync)((0, import_node_path22.join)(dir, safe))) return safe;
10266
10639
  const ext = (0, import_node_path22.extname)(safe);
10267
10640
  const stem = ext ? safe.slice(0, -ext.length) : safe;
10268
10641
  for (let i = 1; i < 1e4; i++) {
10269
10642
  const candidate = `${stem}-${i}${ext}`;
10270
- if (!(0, import_node_fs24.existsSync)((0, import_node_path22.join)(dir, candidate))) return candidate;
10643
+ if (!(0, import_node_fs27.existsSync)((0, import_node_path22.join)(dir, candidate))) return candidate;
10271
10644
  }
10272
10645
  return `${stem}-${(0, import_node_crypto5.randomUUID)()}${ext}`;
10273
10646
  }
@@ -10325,13 +10698,13 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
10325
10698
  for (const abs of absolutePaths) {
10326
10699
  const originalName = (0, import_node_path22.basename)(abs);
10327
10700
  try {
10328
- const st = (0, import_node_fs24.statSync)(abs);
10701
+ const st = (0, import_node_fs27.statSync)(abs);
10329
10702
  if (!st.isFile()) continue;
10330
10703
  const name = uniqueAttachmentName(dir, originalName);
10331
10704
  const destAbs = (0, import_node_path22.join)(dir, name);
10332
- (0, import_node_fs24.copyFileSync)(abs, destAbs);
10705
+ (0, import_node_fs27.copyFileSync)(abs, destAbs);
10333
10706
  const rel = `${ATTACHMENTS_DIR}/${name}`;
10334
- const buf = (0, import_node_fs24.readFileSync)(destAbs);
10707
+ const buf = (0, import_node_fs27.readFileSync)(destAbs);
10335
10708
  out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
10336
10709
  } catch (err) {
10337
10710
  out.push({
@@ -10354,7 +10727,7 @@ function stageBuffersAsAttachments(worktreePath, buffers) {
10354
10727
  const buf = Buffer.from(item.dataBase64, "base64");
10355
10728
  const name = uniqueAttachmentName(dir, originalName);
10356
10729
  const destAbs = (0, import_node_path22.join)(dir, name);
10357
- (0, import_node_fs24.writeFileSync)(destAbs, buf);
10730
+ (0, import_node_fs27.writeFileSync)(destAbs, buf);
10358
10731
  const rel = `${ATTACHMENTS_DIR}/${name}`;
10359
10732
  out.push(attachmentFromBuffer(name, buf, { path: rel }));
10360
10733
  } catch (err) {
@@ -10383,9 +10756,9 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
10383
10756
  const name = (0, import_node_path22.basename)(rel);
10384
10757
  try {
10385
10758
  const abs = (0, import_node_path22.join)(worktreePath, rel);
10386
- const st = (0, import_node_fs24.statSync)(abs);
10759
+ const st = (0, import_node_fs27.statSync)(abs);
10387
10760
  if (!st.isFile()) continue;
10388
- const buf = (0, import_node_fs24.readFileSync)(abs);
10761
+ const buf = (0, import_node_fs27.readFileSync)(abs);
10389
10762
  out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
10390
10763
  } catch (err) {
10391
10764
  out.push({
@@ -10400,7 +10773,7 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
10400
10773
  }
10401
10774
 
10402
10775
  // src/agents/instructions.ts
10403
- var import_node_fs25 = require("fs");
10776
+ var import_node_fs28 = require("fs");
10404
10777
  var import_node_path23 = require("path");
10405
10778
  init_worktree_labels();
10406
10779
  function normPath2(p) {
@@ -10500,6 +10873,9 @@ function formatWorktreeDirective(thread, opts) {
10500
10873
  );
10501
10874
  return lines.join("\n");
10502
10875
  }
10876
+ function formatWorktreeReminder() {
10877
+ 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.";
10878
+ }
10503
10879
  function formatArtifactDirective() {
10504
10880
  return [
10505
10881
  "Sideboard side column (desktop UI):",
@@ -10520,60 +10896,6 @@ function formatArtifactDirective() {
10520
10896
  "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."
10521
10897
  ].join("\n");
10522
10898
  }
10523
- var FILES_BY_AGENT = {
10524
- claude: [
10525
- "CLAUDE.md",
10526
- ".claude/CLAUDE.md",
10527
- "CLAUDE.local.md",
10528
- "AGENTS.md",
10529
- "AGENT.md"
10530
- ],
10531
- codex: ["AGENTS.md", "AGENT.md", ".codex/AGENTS.md", "CLAUDE.md"],
10532
- opencode: ["AGENTS.md", "AGENT.md", "OPENCODE.md", "CLAUDE.md"],
10533
- brightsy: ["AGENTS.md", "AGENT.md", "CLAUDE.md"],
10534
- cursor: ["AGENTS.md", "AGENT.md", ".cursor/rules", "CLAUDE.md"]
10535
- };
10536
- var MAX_CHARS_PER_FILE = 48e3;
10537
- function loadAgentInstructions(worktreePath, agent) {
10538
- const candidates = FILES_BY_AGENT[agent] ?? FILES_BY_AGENT.claude;
10539
- const seen = /* @__PURE__ */ new Set();
10540
- const out = [];
10541
- for (const rel of candidates) {
10542
- if (seen.has(rel)) continue;
10543
- const abs = (0, import_node_path23.join)(worktreePath, rel);
10544
- if (!(0, import_node_fs25.existsSync)(abs)) continue;
10545
- try {
10546
- if (!(0, import_node_fs25.statSync)(abs).isFile()) continue;
10547
- let content = (0, import_node_fs25.readFileSync)(abs, "utf8");
10548
- if (!content.trim()) continue;
10549
- if (content.length > MAX_CHARS_PER_FILE) {
10550
- content = `${content.slice(0, MAX_CHARS_PER_FILE)}
10551
-
10552
- \u2026(truncated)`;
10553
- }
10554
- seen.add(rel);
10555
- out.push({ relativePath: rel, content });
10556
- } catch {
10557
- }
10558
- }
10559
- return out;
10560
- }
10561
- function formatAgentInstructions(files) {
10562
- if (files.length === 0) return null;
10563
- const parts = [
10564
- "Project agent instructions (from the worktree \u2014 follow these):",
10565
- ""
10566
- ];
10567
- for (const f of files) {
10568
- parts.push(`## ${f.relativePath}`);
10569
- parts.push("");
10570
- parts.push(f.content.trim());
10571
- parts.push("");
10572
- parts.push("---");
10573
- parts.push("");
10574
- }
10575
- return parts.join("\n").trimEnd();
10576
- }
10577
10899
 
10578
10900
  // src/orchestrator/orchestrator.ts
10579
10901
  init_types();
@@ -10698,7 +11020,7 @@ var Orchestrator = class {
10698
11020
  }
10699
11021
  continue;
10700
11022
  }
10701
- if (!(0, import_node_fs28.existsSync)(thread.worktreePath)) {
11023
+ if (!(0, import_node_fs31.existsSync)(thread.worktreePath)) {
10702
11024
  setStatus(thread.id, "broken", "Worktree missing on disk");
10703
11025
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
10704
11026
  continue;
@@ -11115,13 +11437,14 @@ var Orchestrator = class {
11115
11437
  "Files column: MCP present_files (brightsy account storage or memory).",
11116
11438
  "Do not say artifacts/CMS UI are unavailable."
11117
11439
  ].join(" ") : null;
11440
+ const worktreeReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatWorktreeReminder() : null;
11118
11441
  const agentPrompt = [
11119
11442
  thread.planMode ? PLAN_MODE_INSTRUCTION : null,
11120
11443
  orchestrationReminder,
11444
+ worktreeReminder,
11121
11445
  artifactReminder,
11122
11446
  expandedPrompt
11123
11447
  ].filter(Boolean).join("\n\n");
11124
- const instructionFiles = loadAgentInstructions(thread.worktreePath, thread.agent);
11125
11448
  if (thread.attachments.length > 0) {
11126
11449
  updateThread(threadId, { attachments: [] });
11127
11450
  }
@@ -11149,7 +11472,6 @@ var Orchestrator = class {
11149
11472
  const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled2() ? formatRenameBranchDirective(fresh, {
11150
11473
  customPrompt: settings?.prompts?.renameBranch
11151
11474
  }) : null;
11152
- const instructions = fresh.agent === "claude" || isBrightsy ? null : formatAgentInstructions(instructionFiles);
11153
11475
  let seed = null;
11154
11476
  if (!fresh.sessionId) {
11155
11477
  const prior = fresh.messages.slice(0, -1);
@@ -11170,12 +11492,12 @@ var Orchestrator = class {
11170
11492
  });
11171
11493
  }
11172
11494
  }
11173
- const cachedPrefix = [
11495
+ const cachedPrefix = fresh.sessionId ? "" : [
11174
11496
  coordinatorDirective,
11175
11497
  worktreeDirective,
11176
11498
  artifactDirective,
11177
11499
  renameBranchDirective,
11178
- ...fresh.agent === "claude" && fresh.sessionId ? [] : [instructions, seed]
11500
+ seed
11179
11501
  ].filter(Boolean).join("\n\n---\n\n");
11180
11502
  try {
11181
11503
  const stderrTail = [];
@@ -11262,15 +11584,11 @@ var Orchestrator = class {
11262
11584
  const retryThread = this.requireThread(threadId);
11263
11585
  const prior = retryThread.messages.slice(0, -1);
11264
11586
  const retrySeed = buildSessionSeed(prior);
11265
- const retryInstructions = retryThread.agent === "claude" ? null : formatAgentInstructions(
11266
- loadAgentInstructions(retryThread.worktreePath, retryThread.agent)
11267
- );
11268
11587
  const retryPrefix = [
11269
11588
  coordinatorDirective,
11270
11589
  worktreeDirective,
11271
11590
  artifactDirective,
11272
11591
  renameBranchDirective,
11273
- retryInstructions,
11274
11592
  retrySeed
11275
11593
  ].filter(Boolean).join("\n\n---\n\n");
11276
11594
  const retryHandle = await spawnAgentTurn(
@@ -12105,7 +12423,7 @@ var Orchestrator = class {
12105
12423
  this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
12106
12424
  return restored2;
12107
12425
  }
12108
- if (!(0, import_node_fs28.existsSync)(thread.worktreePath)) {
12426
+ if (!(0, import_node_fs31.existsSync)(thread.worktreePath)) {
12109
12427
  const { createThreadWorktree: createThreadWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
12110
12428
  const { execa: execa7 } = await import("execa");
12111
12429
  const slug = thread.worktreePath.split("/").pop();
@@ -12314,6 +12632,7 @@ function mcpArchiveBlockedReason(thread) {
12314
12632
  }
12315
12633
 
12316
12634
  // src/mcp/server.ts
12635
+ init_profile();
12317
12636
  var MAX_ORCH_THREADS = 5;
12318
12637
  var CREATE_THREAD_TIMEOUT_MS = 9e4;
12319
12638
  function withTimeout(promise, ms, label) {
@@ -12427,14 +12746,13 @@ async function startMcpServer() {
12427
12746
  ),
12428
12747
  artifact_id: import_zod.z.string().optional().describe("Stable id when updating the same artifact across turns")
12429
12748
  },
12430
- async ({ title, type, content, artifact_id }) => {
12749
+ async ({ title, type, artifact_id }) => {
12431
12750
  const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
12432
12751
  const payload = {
12433
12752
  ok: true,
12434
12753
  artifact_id: id,
12435
12754
  title,
12436
12755
  type,
12437
- content,
12438
12756
  message: "Artifact accepted. Sideboard desktop opens it in the side column beside chat."
12439
12757
  };
12440
12758
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
@@ -12487,7 +12805,6 @@ async function startMcpServer() {
12487
12805
  ok: true,
12488
12806
  path,
12489
12807
  title: title?.trim() || "Plan",
12490
- content,
12491
12808
  message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
12492
12809
  };
12493
12810
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
@@ -12517,9 +12834,6 @@ async function startMcpServer() {
12517
12834
  datasource: args.datasource ?? (args.resource ? "inline" : "brightsy"),
12518
12835
  resource_id: args.resource_id,
12519
12836
  record_id: args.record_id,
12520
- resource: args.resource,
12521
- record: args.record,
12522
- records: args.records,
12523
12837
  message: "Schema pane accepted. Sideboard desktop opens the CMS column beside chat."
12524
12838
  };
12525
12839
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
@@ -12547,739 +12861,741 @@ async function startMcpServer() {
12547
12861
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
12548
12862
  }
12549
12863
  );
12550
- const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
12551
- const accountDefaults = resolveThreadDefaults2();
12552
- const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
12553
- server.tool(
12554
- "create_thread",
12555
- `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.`,
12556
- {
12557
- sourceType: import_zod.z.enum(["branch", "pr", "ticket"]),
12558
- sourceRef: import_zod.z.string(),
12559
- agent: import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
12560
- model: import_zod.z.string().nullable().optional().describe(
12561
- `Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
12562
- ),
12563
- repoPath: import_zod.z.string(),
12564
- title: import_zod.z.string().optional(),
12565
- parentThreadId: import_zod.z.string().optional().describe(
12566
- "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
12567
- )
12568
- },
12569
- async (args) => {
12570
- const envParentId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || "";
12571
- let parentId = args.parentThreadId?.trim() || "";
12572
- let parent = parentId ? orch.getThread(parentId) : null;
12573
- let parentCorrectedFrom;
12574
- if (envParentId) {
12575
- const envParent = orch.getThread(envParentId);
12576
- if (envParent) {
12577
- if (parentId && parentId !== envParentId) parentCorrectedFrom = parentId;
12578
- else if (!parentId) parentCorrectedFrom = void 0;
12579
- parentId = envParentId;
12580
- parent = envParent;
12864
+ if (sideboardMcpProfile() !== "worktree") {
12865
+ const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
12866
+ const accountDefaults = resolveThreadDefaults2();
12867
+ const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
12868
+ server.tool(
12869
+ "create_thread",
12870
+ `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.`,
12871
+ {
12872
+ sourceType: import_zod.z.enum(["branch", "pr", "ticket"]),
12873
+ sourceRef: import_zod.z.string(),
12874
+ agent: import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
12875
+ model: import_zod.z.string().nullable().optional().describe(
12876
+ `Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
12877
+ ),
12878
+ repoPath: import_zod.z.string(),
12879
+ title: import_zod.z.string().optional(),
12880
+ parentThreadId: import_zod.z.string().optional().describe(
12881
+ "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
12882
+ )
12883
+ },
12884
+ async (args) => {
12885
+ const envParentId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || "";
12886
+ let parentId = args.parentThreadId?.trim() || "";
12887
+ let parent = parentId ? orch.getThread(parentId) : null;
12888
+ let parentCorrectedFrom;
12889
+ if (envParentId) {
12890
+ const envParent = orch.getThread(envParentId);
12891
+ if (envParent) {
12892
+ if (parentId && parentId !== envParentId) parentCorrectedFrom = parentId;
12893
+ else if (!parentId) parentCorrectedFrom = void 0;
12894
+ parentId = envParentId;
12895
+ parent = envParent;
12896
+ }
12581
12897
  }
12582
- }
12583
- if (parentId && !parent) {
12584
- parentCorrectedFrom = parentId;
12585
- parentId = "";
12586
- parent = null;
12587
- }
12588
- if (parentId) {
12589
- const children = orch.getThreads(false).filter((t) => t.parentThreadId === parentId);
12590
- if (children.length >= MAX_ORCH_THREADS) {
12898
+ if (parentId && !parent) {
12899
+ parentCorrectedFrom = parentId;
12900
+ parentId = "";
12901
+ parent = null;
12902
+ }
12903
+ if (parentId) {
12904
+ const children = orch.getThreads(false).filter((t) => t.parentThreadId === parentId);
12905
+ if (children.length >= MAX_ORCH_THREADS) {
12906
+ return {
12907
+ content: [
12908
+ {
12909
+ type: "text",
12910
+ text: `Thread-creation cap (${MAX_ORCH_THREADS}) reached for this orchestration session`
12911
+ }
12912
+ ],
12913
+ isError: true
12914
+ };
12915
+ }
12916
+ }
12917
+ let agentArg = args.agent;
12918
+ let agentCoercedFrom;
12919
+ const resolvedProbe = resolveNewThreadOptions2({ agent: agentArg }).agent;
12920
+ if (resolvedProbe === "codex") {
12921
+ agentCoercedFrom = agentArg ?? "codex";
12922
+ const accountAgent = resolveNewThreadOptions2({}).agent;
12923
+ agentArg = accountAgent !== "codex" ? accountAgent : "cursor";
12924
+ }
12925
+ const opts = resolveNewThreadOptions2({
12926
+ agent: agentArg,
12927
+ model: args.model
12928
+ });
12929
+ try {
12930
+ const thread = await withTimeout(
12931
+ orch.createThread({
12932
+ sourceType: args.sourceType,
12933
+ sourceRef: args.sourceRef,
12934
+ agent: opts.agent,
12935
+ model: opts.model,
12936
+ effort: opts.effort,
12937
+ fast: opts.fast,
12938
+ repoPath: args.repoPath,
12939
+ title: args.title,
12940
+ parentThreadId: parentId || null
12941
+ }),
12942
+ CREATE_THREAD_TIMEOUT_MS,
12943
+ "create_thread"
12944
+ );
12945
+ const parentNote = parentCorrectedFrom ? parentId ? `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; nested under ${parentId}` : `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; created without parent` : void 0;
12591
12946
  return {
12592
12947
  content: [
12593
12948
  {
12594
12949
  type: "text",
12595
- text: `Thread-creation cap (${MAX_ORCH_THREADS}) reached for this orchestration session`
12950
+ text: JSON.stringify({
12951
+ id: thread.id,
12952
+ title: thread.title,
12953
+ branchName: thread.branchName,
12954
+ worktreePath: thread.worktreePath,
12955
+ agent: thread.agent,
12956
+ model: thread.model,
12957
+ status: thread.status,
12958
+ link: `sideboard://thread/${thread.id}`,
12959
+ parentThreadId: thread.parentThreadId,
12960
+ ...agentCoercedFrom ? {
12961
+ agentCoercedFrom,
12962
+ note: `Avoid nested Codex under a Codex orchestrator \u2014 used Account default agent=${thread.agent}`
12963
+ } : {},
12964
+ ...parentCorrectedFrom ? { parentCorrectedFrom, parentNote } : {}
12965
+ })
12966
+ }
12967
+ ]
12968
+ };
12969
+ } catch (err) {
12970
+ const message = err instanceof Error ? err.message : String(err);
12971
+ return {
12972
+ content: [
12973
+ {
12974
+ type: "text",
12975
+ text: `create_thread failed: ${message}`
12596
12976
  }
12597
12977
  ],
12598
12978
  isError: true
12599
12979
  };
12600
12980
  }
12601
12981
  }
12602
- let agentArg = args.agent;
12603
- let agentCoercedFrom;
12604
- const resolvedProbe = resolveNewThreadOptions2({ agent: agentArg }).agent;
12605
- if (resolvedProbe === "codex") {
12606
- agentCoercedFrom = agentArg ?? "codex";
12607
- const accountAgent = resolveNewThreadOptions2({}).agent;
12608
- agentArg = accountAgent !== "codex" ? accountAgent : "cursor";
12609
- }
12610
- const opts = resolveNewThreadOptions2({
12611
- agent: agentArg,
12612
- model: args.model
12613
- });
12614
- try {
12615
- const thread = await withTimeout(
12616
- orch.createThread({
12617
- sourceType: args.sourceType,
12618
- sourceRef: args.sourceRef,
12619
- agent: opts.agent,
12620
- model: opts.model,
12621
- effort: opts.effort,
12622
- fast: opts.fast,
12623
- repoPath: args.repoPath,
12624
- title: args.title,
12625
- parentThreadId: parentId || null
12626
- }),
12627
- CREATE_THREAD_TIMEOUT_MS,
12628
- "create_thread"
12629
- );
12630
- const parentNote = parentCorrectedFrom ? parentId ? `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; nested under ${parentId}` : `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; created without parent` : void 0;
12982
+ );
12983
+ server.tool(
12984
+ "send_to_thread",
12985
+ "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.",
12986
+ {
12987
+ ref: import_zod.z.string(),
12988
+ prompt: import_zod.z.string(),
12989
+ force_stop: import_zod.z.boolean().optional()
12990
+ },
12991
+ async ({ ref, prompt, force_stop }) => {
12992
+ if (force_stop) {
12993
+ const existing = orch.getThread(ref);
12994
+ if (existing) {
12995
+ orch.stop(ref, { clearQueue: true });
12996
+ }
12997
+ }
12998
+ const thread = await orch.send(ref, prompt);
12631
12999
  return {
12632
13000
  content: [
12633
13001
  {
12634
13002
  type: "text",
12635
13003
  text: JSON.stringify({
12636
13004
  id: thread.id,
12637
- title: thread.title,
12638
- branchName: thread.branchName,
12639
- worktreePath: thread.worktreePath,
12640
- agent: thread.agent,
12641
- model: thread.model,
12642
13005
  status: thread.status,
12643
- link: `sideboard://thread/${thread.id}`,
12644
- parentThreadId: thread.parentThreadId,
12645
- ...agentCoercedFrom ? {
12646
- agentCoercedFrom,
12647
- note: `Avoid nested Codex under a Codex orchestrator \u2014 used Account default agent=${thread.agent}`
12648
- } : {},
12649
- ...parentCorrectedFrom ? { parentCorrectedFrom, parentNote } : {}
13006
+ queueLength: thread.queue.length,
13007
+ forceStopped: Boolean(force_stop)
12650
13008
  })
12651
13009
  }
12652
13010
  ]
12653
13011
  };
12654
- } catch (err) {
12655
- const message = err instanceof Error ? err.message : String(err);
13012
+ }
13013
+ );
13014
+ server.tool(
13015
+ "wait_for_turn",
13016
+ "Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread to read the agent reply.",
13017
+ {
13018
+ ref: import_zod.z.string(),
13019
+ timeoutMs: import_zod.z.number().optional()
13020
+ },
13021
+ async ({ ref, timeoutMs }) => {
13022
+ const thread = await orch.waitForTurn(ref, timeoutMs ?? 6e5);
13023
+ const result = orch.getTurnResult(thread.id);
12656
13024
  return {
12657
13025
  content: [
12658
13026
  {
12659
13027
  type: "text",
12660
- text: `create_thread failed: ${message}`
13028
+ text: JSON.stringify({
13029
+ id: thread.id,
13030
+ status: result.status,
13031
+ text: result.text
13032
+ })
12661
13033
  }
12662
- ],
12663
- isError: true
13034
+ ]
12664
13035
  };
12665
13036
  }
12666
- }
12667
- );
12668
- server.tool(
12669
- "send_to_thread",
12670
- "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.",
12671
- {
12672
- ref: import_zod.z.string(),
12673
- prompt: import_zod.z.string(),
12674
- force_stop: import_zod.z.boolean().optional()
12675
- },
12676
- async ({ ref, prompt, force_stop }) => {
12677
- if (force_stop) {
12678
- const existing = orch.getThread(ref);
12679
- if (existing) {
12680
- orch.stop(ref, { clearQueue: true });
12681
- }
13037
+ );
13038
+ server.tool(
13039
+ "get_turn_result",
13040
+ "Final assistant message only (not full transcript)",
13041
+ { ref: import_zod.z.string() },
13042
+ async ({ ref }) => {
13043
+ const result = orch.getTurnResult(ref);
13044
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
12682
13045
  }
12683
- const thread = await orch.send(ref, prompt);
12684
- return {
12685
- content: [
12686
- {
12687
- type: "text",
12688
- text: JSON.stringify({
12689
- id: thread.id,
12690
- status: thread.status,
12691
- queueLength: thread.queue.length,
12692
- forceStopped: Boolean(force_stop)
12693
- })
12694
- }
12695
- ]
12696
- };
12697
- }
12698
- );
12699
- server.tool(
12700
- "wait_for_turn",
12701
- "Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread to read the agent reply.",
12702
- {
12703
- ref: import_zod.z.string(),
12704
- timeoutMs: import_zod.z.number().optional()
12705
- },
12706
- async ({ ref, timeoutMs }) => {
12707
- const thread = await orch.waitForTurn(ref, timeoutMs ?? 6e5);
12708
- const result = orch.getTurnResult(thread.id);
12709
- return {
12710
- content: [
12711
- {
12712
- type: "text",
12713
- text: JSON.stringify({
12714
- id: thread.id,
12715
- status: result.status,
12716
- text: result.text
12717
- })
12718
- }
12719
- ]
12720
- };
12721
- }
12722
- );
12723
- server.tool(
12724
- "get_turn_result",
12725
- "Final assistant message only (not full transcript)",
12726
- { ref: import_zod.z.string() },
12727
- async ({ ref }) => {
12728
- const result = orch.getTurnResult(ref);
12729
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
12730
- }
12731
- );
12732
- server.tool(
12733
- "stop_thread",
12734
- "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.",
12735
- {
12736
- ref: import_zod.z.string(),
12737
- force: import_zod.z.boolean().optional()
12738
- },
12739
- async ({ ref, force }) => {
12740
- const t = orch.getThread(ref);
12741
- if (!t) {
13046
+ );
13047
+ server.tool(
13048
+ "stop_thread",
13049
+ "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.",
13050
+ {
13051
+ ref: import_zod.z.string(),
13052
+ force: import_zod.z.boolean().optional()
13053
+ },
13054
+ async ({ ref, force }) => {
13055
+ const t = orch.getThread(ref);
13056
+ if (!t) {
13057
+ return {
13058
+ content: [{ type: "text", text: `Thread not found: ${ref}` }],
13059
+ isError: true
13060
+ };
13061
+ }
13062
+ const clearQueue = force !== false;
13063
+ const hadQueued = t.queue.length > 0;
13064
+ const stopped = orch.stop(ref, { clearQueue });
12742
13065
  return {
12743
- content: [{ type: "text", text: `Thread not found: ${ref}` }],
12744
- isError: true
13066
+ content: [
13067
+ {
13068
+ type: "text",
13069
+ text: JSON.stringify({
13070
+ id: stopped.id,
13071
+ status: stopped.status,
13072
+ clearedQueue: clearQueue && hadQueued
13073
+ })
13074
+ }
13075
+ ]
12745
13076
  };
12746
13077
  }
12747
- const clearQueue = force !== false;
12748
- const hadQueued = t.queue.length > 0;
12749
- const stopped = orch.stop(ref, { clearQueue });
12750
- return {
12751
- content: [
12752
- {
12753
- type: "text",
12754
- text: JSON.stringify({
12755
- id: stopped.id,
12756
- status: stopped.status,
12757
- clearedQueue: clearQueue && hadQueued
12758
- })
12759
- }
12760
- ]
12761
- };
12762
- }
12763
- );
12764
- server.tool(
12765
- "archive_thread",
12766
- "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.",
12767
- { ref: import_zod.z.string() },
12768
- async ({ ref }) => {
12769
- const t = orch.getThread(ref);
12770
- if (!t) {
13078
+ );
13079
+ server.tool(
13080
+ "archive_thread",
13081
+ "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.",
13082
+ { ref: import_zod.z.string() },
13083
+ async ({ ref }) => {
13084
+ const t = orch.getThread(ref);
13085
+ if (!t) {
13086
+ return {
13087
+ content: [{ type: "text", text: `Thread not found: ${ref}` }],
13088
+ isError: true
13089
+ };
13090
+ }
13091
+ const blocked = mcpArchiveBlockedReason(t);
13092
+ if (blocked) {
13093
+ return {
13094
+ content: [{ type: "text", text: blocked }],
13095
+ isError: true
13096
+ };
13097
+ }
13098
+ const archived = await orch.archive(ref);
12771
13099
  return {
12772
- content: [{ type: "text", text: `Thread not found: ${ref}` }],
12773
- isError: true
13100
+ content: [
13101
+ {
13102
+ type: "text",
13103
+ text: JSON.stringify({
13104
+ id: archived.id,
13105
+ status: archived.status
13106
+ })
13107
+ }
13108
+ ]
12774
13109
  };
12775
13110
  }
12776
- const blocked = mcpArchiveBlockedReason(t);
12777
- if (blocked) {
12778
- return {
12779
- content: [{ type: "text", text: blocked }],
12780
- isError: true
12781
- };
13111
+ );
13112
+ server.tool(
13113
+ "restore_thread",
13114
+ "Restore an archived thread (recreates worktree from branch when needed)",
13115
+ { ref: import_zod.z.string() },
13116
+ async ({ ref }) => {
13117
+ try {
13118
+ const restored = await orch.restore(ref);
13119
+ return {
13120
+ content: [
13121
+ {
13122
+ type: "text",
13123
+ text: JSON.stringify({
13124
+ id: restored.id,
13125
+ status: restored.status,
13126
+ worktreePath: restored.worktreePath
13127
+ })
13128
+ }
13129
+ ]
13130
+ };
13131
+ } catch (err) {
13132
+ const message = err instanceof Error ? err.message : String(err);
13133
+ return { content: [{ type: "text", text: message }], isError: true };
13134
+ }
12782
13135
  }
12783
- const archived = await orch.archive(ref);
12784
- return {
12785
- content: [
12786
- {
12787
- type: "text",
12788
- text: JSON.stringify({
12789
- id: archived.id,
12790
- status: archived.status
12791
- })
12792
- }
12793
- ]
12794
- };
12795
- }
12796
- );
12797
- server.tool(
12798
- "restore_thread",
12799
- "Restore an archived thread (recreates worktree from branch when needed)",
12800
- { ref: import_zod.z.string() },
12801
- async ({ ref }) => {
12802
- try {
12803
- const restored = await orch.restore(ref);
13136
+ );
13137
+ server.tool(
13138
+ "get_diff",
13139
+ "Compact diff summary (capped hunks, paginated)",
13140
+ {
13141
+ ref: import_zod.z.string(),
13142
+ maxFiles: import_zod.z.number().optional()
13143
+ },
13144
+ async ({ ref, maxFiles }) => {
13145
+ const summary = await orch.diffSummary(ref);
12804
13146
  return {
12805
13147
  content: [
12806
13148
  {
12807
13149
  type: "text",
12808
13150
  text: JSON.stringify({
12809
- id: restored.id,
12810
- status: restored.status,
12811
- worktreePath: restored.worktreePath
13151
+ ...summary,
13152
+ files: summary.files.slice(0, maxFiles ?? 10)
12812
13153
  })
12813
13154
  }
12814
13155
  ]
12815
13156
  };
12816
- } catch (err) {
12817
- const message = err instanceof Error ? err.message : String(err);
12818
- return { content: [{ type: "text", text: message }], isError: true };
12819
13157
  }
12820
- }
12821
- );
12822
- server.tool(
12823
- "get_diff",
12824
- "Compact diff summary (capped hunks, paginated)",
12825
- {
12826
- ref: import_zod.z.string(),
12827
- maxFiles: import_zod.z.number().optional()
12828
- },
12829
- async ({ ref, maxFiles }) => {
12830
- const summary = await orch.diffSummary(ref);
12831
- return {
12832
- content: [
12833
- {
12834
- type: "text",
12835
- text: JSON.stringify({
12836
- ...summary,
12837
- files: summary.files.slice(0, maxFiles ?? 10)
12838
- })
13158
+ );
13159
+ server.tool(
13160
+ "request_review",
13161
+ '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.',
13162
+ { ref: import_zod.z.string().describe("Worktree thread id/ref to review") },
13163
+ async ({ ref }) => {
13164
+ try {
13165
+ const tab = await orch.requestReview(ref);
13166
+ const from = orch.getThread(ref);
13167
+ return {
13168
+ content: [
13169
+ {
13170
+ type: "text",
13171
+ text: JSON.stringify({
13172
+ id: tab.id,
13173
+ title: tab.title,
13174
+ status: tab.status,
13175
+ fromThreadId: from?.id ?? ref,
13176
+ link: `sideboard://thread/${tab.id}`
13177
+ })
13178
+ }
13179
+ ]
13180
+ };
13181
+ } catch (err) {
13182
+ const message = err instanceof Error ? err.message : String(err);
13183
+ return { content: [{ type: "text", text: message }], isError: true };
13184
+ }
13185
+ }
13186
+ );
13187
+ const agentEnum = import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
13188
+ server.tool(
13189
+ "list_models",
13190
+ "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.",
13191
+ {
13192
+ agent: agentEnum.optional().describe("Limit to one agent; omit for all")
13193
+ },
13194
+ async ({ agent }) => {
13195
+ try {
13196
+ const catalogs = await listModelsForAgent(agent);
13197
+ return {
13198
+ content: [{ type: "text", text: JSON.stringify(catalogs, null, 2) }]
13199
+ };
13200
+ } catch (err) {
13201
+ const message = err instanceof Error ? err.message : String(err);
13202
+ return { content: [{ type: "text", text: message }], isError: true };
13203
+ }
13204
+ }
13205
+ );
13206
+ server.tool(
13207
+ "fork_worktree",
13208
+ "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.",
13209
+ {
13210
+ ref: import_zod.z.string().describe("Worktree thread id/ref to fork"),
13211
+ through_index: import_zod.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
13212
+ agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
13213
+ model: import_zod.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
13214
+ title: import_zod.z.string().optional()
13215
+ },
13216
+ async ({ ref, through_index, agent, model, title }) => {
13217
+ try {
13218
+ const source = orch.getThread(ref);
13219
+ if (source) await orch.reconcile(source.repoPath, { drainQueues: false });
13220
+ const thread = await orch.forkThreadWorktree({
13221
+ threadId: ref,
13222
+ throughIndex: through_index,
13223
+ agent,
13224
+ model,
13225
+ title
13226
+ });
13227
+ return {
13228
+ content: [
13229
+ {
13230
+ type: "text",
13231
+ text: JSON.stringify({
13232
+ id: thread.id,
13233
+ title: thread.title,
13234
+ status: thread.status,
13235
+ agent: thread.agent,
13236
+ model: thread.model,
13237
+ branchName: thread.branchName,
13238
+ worktreePath: thread.worktreePath,
13239
+ fromThreadId: source?.id ?? ref,
13240
+ link: `sideboard://thread/${thread.id}`
13241
+ })
13242
+ }
13243
+ ]
13244
+ };
13245
+ } catch (err) {
13246
+ const message = err instanceof Error ? err.message : String(err);
13247
+ return { content: [{ type: "text", text: message }], isError: true };
13248
+ }
13249
+ }
13250
+ );
13251
+ server.tool(
13252
+ "fork_chat",
13253
+ "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.",
13254
+ {
13255
+ ref: import_zod.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
13256
+ through_index: import_zod.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
13257
+ agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
13258
+ model: import_zod.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
13259
+ title: import_zod.z.string().optional()
13260
+ },
13261
+ async ({ ref, through_index, agent, model, title }) => {
13262
+ try {
13263
+ const source = orch.getThread(ref);
13264
+ if (!source) {
13265
+ return {
13266
+ content: [{ type: "text", text: `Thread not found: ${ref}` }],
13267
+ isError: true
13268
+ };
12839
13269
  }
12840
- ]
12841
- };
12842
- }
12843
- );
12844
- server.tool(
12845
- "request_review",
12846
- '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.',
12847
- { ref: import_zod.z.string().describe("Worktree thread id/ref to review") },
12848
- async ({ ref }) => {
12849
- try {
12850
- const tab = await orch.requestReview(ref);
12851
- const from = orch.getThread(ref);
13270
+ const tab = orch.forkChatTab({
13271
+ threadId: source.id,
13272
+ throughIndex: through_index,
13273
+ agent,
13274
+ model,
13275
+ title
13276
+ });
13277
+ return {
13278
+ content: [
13279
+ {
13280
+ type: "text",
13281
+ text: JSON.stringify({
13282
+ id: tab.id,
13283
+ title: tab.title,
13284
+ status: tab.status,
13285
+ agent: tab.agent,
13286
+ model: tab.model,
13287
+ sourceType: tab.sourceType,
13288
+ worktreePath: tab.worktreePath,
13289
+ fromThreadId: source.id,
13290
+ link: `sideboard://thread/${tab.id}`
13291
+ })
13292
+ }
13293
+ ]
13294
+ };
13295
+ } catch (err) {
13296
+ const message = err instanceof Error ? err.message : String(err);
13297
+ return { content: [{ type: "text", text: message }], isError: true };
13298
+ }
13299
+ }
13300
+ );
13301
+ server.tool(
13302
+ "run_dev_script",
13303
+ "Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
13304
+ {
13305
+ ref: import_zod.z.string(),
13306
+ name: import_zod.z.string().optional()
13307
+ },
13308
+ async ({ ref, name }) => {
13309
+ const result = await orch.startDev(ref, name);
12852
13310
  return {
12853
13311
  content: [
12854
13312
  {
12855
13313
  type: "text",
12856
13314
  text: JSON.stringify({
12857
- id: tab.id,
12858
- title: tab.title,
12859
- status: tab.status,
12860
- fromThreadId: from?.id ?? ref,
12861
- link: `sideboard://thread/${tab.id}`
13315
+ port: result.port,
13316
+ scriptName: result.scriptName,
13317
+ ports: result.ports,
13318
+ url: `http://localhost:${result.port}`
12862
13319
  })
12863
13320
  }
12864
13321
  ]
12865
13322
  };
12866
- } catch (err) {
12867
- const message = err instanceof Error ? err.message : String(err);
12868
- return { content: [{ type: "text", text: message }], isError: true };
12869
13323
  }
12870
- }
12871
- );
12872
- const agentEnum = import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
12873
- server.tool(
12874
- "list_models",
12875
- "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.",
12876
- {
12877
- agent: agentEnum.optional().describe("Limit to one agent; omit for all")
12878
- },
12879
- async ({ agent }) => {
12880
- try {
12881
- const catalogs = await listModelsForAgent(agent);
13324
+ );
13325
+ server.tool(
13326
+ "list_run_scripts",
13327
+ "List named run scripts available for a thread",
13328
+ { ref: import_zod.z.string() },
13329
+ async ({ ref }) => {
13330
+ const scripts = orch.listThreadRunScripts(ref);
13331
+ const active = orch.getActiveRuns(ref);
12882
13332
  return {
12883
- content: [{ type: "text", text: JSON.stringify(catalogs, null, 2) }]
13333
+ content: [
13334
+ {
13335
+ type: "text",
13336
+ text: JSON.stringify({ scripts, active }, null, 2)
13337
+ }
13338
+ ]
12884
13339
  };
12885
- } catch (err) {
12886
- const message = err instanceof Error ? err.message : String(err);
12887
- return { content: [{ type: "text", text: message }], isError: true };
12888
13340
  }
12889
- }
12890
- );
12891
- server.tool(
12892
- "fork_worktree",
12893
- "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.",
12894
- {
12895
- ref: import_zod.z.string().describe("Worktree thread id/ref to fork"),
12896
- through_index: import_zod.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
12897
- agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
12898
- model: import_zod.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
12899
- title: import_zod.z.string().optional()
12900
- },
12901
- async ({ ref, through_index, agent, model, title }) => {
12902
- try {
12903
- const source = orch.getThread(ref);
12904
- if (source) await orch.reconcile(source.repoPath, { drainQueues: false });
12905
- const thread = await orch.forkThreadWorktree({
12906
- threadId: ref,
12907
- throughIndex: through_index,
12908
- agent,
12909
- model,
12910
- title
13341
+ );
13342
+ server.tool(
13343
+ "stop_dev_script",
13344
+ "Stop a running script for a thread (all scripts if name omitted)",
13345
+ {
13346
+ ref: import_zod.z.string(),
13347
+ name: import_zod.z.string().optional()
13348
+ },
13349
+ async ({ ref, name }) => {
13350
+ orch.stopDev(ref, name);
13351
+ return { content: [{ type: "text", text: "ok" }] };
13352
+ }
13353
+ );
13354
+ server.tool(
13355
+ "run_setup",
13356
+ "Re-run workspace setup (Sideboard/Conductor settings or .cursor/worktrees.json)",
13357
+ { ref: import_zod.z.string() },
13358
+ async ({ ref }) => {
13359
+ const result = await orch.runSetup(ref);
13360
+ return {
13361
+ content: [{ type: "text", text: JSON.stringify(result) }]
13362
+ };
13363
+ }
13364
+ );
13365
+ server.tool(
13366
+ "add_workspace",
13367
+ "Register a git repo as a Sideboard workspace",
13368
+ { repoPath: import_zod.z.string() },
13369
+ async ({ repoPath }) => {
13370
+ const ws = await orch.addWorkspace(repoPath);
13371
+ return { content: [{ type: "text", text: JSON.stringify(ws) }] };
13372
+ }
13373
+ );
13374
+ server.tool(
13375
+ "remove_workspace",
13376
+ "Unregister a Sideboard workspace (does not archive threads)",
13377
+ { repoPath: import_zod.z.string() },
13378
+ async ({ repoPath }) => {
13379
+ orch.removeWorkspace(repoPath);
13380
+ return { content: [{ type: "text", text: "ok" }] };
13381
+ }
13382
+ );
13383
+ server.tool(
13384
+ "fanout",
13385
+ "Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
13386
+ {
13387
+ prompt: import_zod.z.string(),
13388
+ agents: import_zod.z.array(import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
13389
+ repoPath: import_zod.z.string(),
13390
+ sourceType: import_zod.z.enum(["branch", "pr", "ticket"]).optional(),
13391
+ sourceRef: import_zod.z.string().optional(),
13392
+ title: import_zod.z.string().optional()
13393
+ },
13394
+ async (args) => {
13395
+ const threads = await orch.bestOfN(args);
13396
+ return {
13397
+ content: [
13398
+ {
13399
+ type: "text",
13400
+ text: JSON.stringify(
13401
+ threads.map((t) => ({
13402
+ id: t.id,
13403
+ agent: t.agent,
13404
+ branchName: t.branchName,
13405
+ worktreePath: t.worktreePath
13406
+ })),
13407
+ null,
13408
+ 2
13409
+ )
13410
+ }
13411
+ ]
13412
+ };
13413
+ }
13414
+ );
13415
+ server.tool(
13416
+ "list_branches",
13417
+ "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).",
13418
+ {
13419
+ repoPath: import_zod.z.string(),
13420
+ unmergedOnly: import_zod.z.boolean().optional()
13421
+ },
13422
+ async ({ repoPath, unmergedOnly }) => {
13423
+ const root = await resolveRepoRoot(repoPath);
13424
+ const branches = await listBranches(root, {
13425
+ unmergedOnly: unmergedOnly !== false
12911
13426
  });
12912
13427
  return {
12913
13428
  content: [
12914
13429
  {
12915
13430
  type: "text",
12916
- text: JSON.stringify({
12917
- id: thread.id,
12918
- title: thread.title,
12919
- status: thread.status,
12920
- agent: thread.agent,
12921
- model: thread.model,
12922
- branchName: thread.branchName,
12923
- worktreePath: thread.worktreePath,
12924
- fromThreadId: source?.id ?? ref,
12925
- link: `sideboard://thread/${thread.id}`
12926
- })
13431
+ text: branches.map((b) => `${b.current ? "*" : " "} ${b.name}`).join("\n")
12927
13432
  }
12928
13433
  ]
12929
13434
  };
12930
- } catch (err) {
12931
- const message = err instanceof Error ? err.message : String(err);
12932
- return { content: [{ type: "text", text: message }], isError: true };
12933
13435
  }
12934
- }
12935
- );
12936
- server.tool(
12937
- "fork_chat",
12938
- "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.",
12939
- {
12940
- ref: import_zod.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
12941
- through_index: import_zod.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
12942
- agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
12943
- model: import_zod.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
12944
- title: import_zod.z.string().optional()
12945
- },
12946
- async ({ ref, through_index, agent, model, title }) => {
12947
- try {
12948
- const source = orch.getThread(ref);
12949
- if (!source) {
12950
- return {
12951
- content: [{ type: "text", text: `Thread not found: ${ref}` }],
12952
- isError: true
12953
- };
12954
- }
12955
- const tab = orch.forkChatTab({
12956
- threadId: source.id,
12957
- throughIndex: through_index,
12958
- agent,
12959
- model,
12960
- title
13436
+ );
13437
+ server.tool(
13438
+ "list_prs",
13439
+ "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.",
13440
+ { repoPath: import_zod.z.string() },
13441
+ async ({ repoPath }) => {
13442
+ const root = await resolveRepoRoot(repoPath);
13443
+ const prs = await listPrs(root);
13444
+ return {
13445
+ content: [
13446
+ {
13447
+ type: "text",
13448
+ text: prs.map(
13449
+ (p) => `#${p.number} ${p.title} (${p.headRefName})${p.isCrossRepository ? " [fork]" : ""}`
13450
+ ).join("\n")
13451
+ }
13452
+ ]
13453
+ };
13454
+ }
13455
+ );
13456
+ server.tool(
13457
+ "get_pr_stack",
13458
+ "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.",
13459
+ { ref: import_zod.z.string() },
13460
+ async ({ ref }) => {
13461
+ const stack = await orch.getPrStack(ref);
13462
+ return {
13463
+ content: [{ type: "text", text: JSON.stringify(stack, null, 2) }]
13464
+ };
13465
+ }
13466
+ );
13467
+ server.tool(
13468
+ "open_pr_stack_layers",
13469
+ "Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
13470
+ {
13471
+ ref: import_zod.z.string(),
13472
+ layer: import_zod.z.number().int().positive().optional()
13473
+ },
13474
+ async ({ ref, layer }) => {
13475
+ const result = await orch.openPrStackLayers(ref, { layer });
13476
+ return {
13477
+ content: [
13478
+ {
13479
+ type: "text",
13480
+ text: JSON.stringify(
13481
+ {
13482
+ stackNumber: result.stack.stackNumber,
13483
+ trunk: result.stack.trunk,
13484
+ threads: result.threads.map((t) => ({
13485
+ id: t.id,
13486
+ title: t.title,
13487
+ branchName: t.branchName,
13488
+ stackLayer: t.stackLayer,
13489
+ worktreePath: t.worktreePath,
13490
+ prUrl: t.prUrl,
13491
+ link: `sideboard://thread/${t.id}`
13492
+ }))
13493
+ },
13494
+ null,
13495
+ 2
13496
+ )
13497
+ }
13498
+ ]
13499
+ };
13500
+ }
13501
+ );
13502
+ server.tool(
13503
+ "add_stack_layer",
13504
+ "Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
13505
+ {
13506
+ ref: import_zod.z.string(),
13507
+ branchName: import_zod.z.string(),
13508
+ title: import_zod.z.string().optional()
13509
+ },
13510
+ async ({ ref, branchName, title }) => {
13511
+ const result = await orch.addStackLayer(ref, branchName, { title });
13512
+ return {
13513
+ content: [
13514
+ {
13515
+ type: "text",
13516
+ text: JSON.stringify(
13517
+ {
13518
+ id: result.thread.id,
13519
+ title: result.thread.title,
13520
+ branchName: result.thread.branchName,
13521
+ stackLayer: result.thread.stackLayer,
13522
+ worktreePath: result.thread.worktreePath,
13523
+ link: `sideboard://thread/${result.thread.id}`
13524
+ },
13525
+ null,
13526
+ 2
13527
+ )
13528
+ }
13529
+ ]
13530
+ };
13531
+ }
13532
+ );
13533
+ server.tool(
13534
+ "create_pr_stack",
13535
+ "Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
13536
+ {
13537
+ repoPath: import_zod.z.string(),
13538
+ branches: import_zod.z.array(import_zod.z.string()).min(1),
13539
+ agent: import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
13540
+ base: import_zod.z.string().optional(),
13541
+ title: import_zod.z.string().optional()
13542
+ },
13543
+ async (args) => {
13544
+ const result = await orch.createPrStack({
13545
+ repoPath: args.repoPath,
13546
+ branches: args.branches,
13547
+ agent: args.agent,
13548
+ base: args.base,
13549
+ title: args.title
12961
13550
  });
12962
13551
  return {
12963
13552
  content: [
12964
13553
  {
12965
13554
  type: "text",
12966
- text: JSON.stringify({
12967
- id: tab.id,
12968
- title: tab.title,
12969
- status: tab.status,
12970
- agent: tab.agent,
12971
- model: tab.model,
12972
- sourceType: tab.sourceType,
12973
- worktreePath: tab.worktreePath,
12974
- fromThreadId: source.id,
12975
- link: `sideboard://thread/${tab.id}`
12976
- })
13555
+ text: JSON.stringify(
13556
+ {
13557
+ stackNumber: result.stack.stackNumber,
13558
+ trunk: result.stack.trunk,
13559
+ threads: result.threads.map((t) => ({
13560
+ id: t.id,
13561
+ title: t.title,
13562
+ branchName: t.branchName,
13563
+ stackLayer: t.stackLayer,
13564
+ worktreePath: t.worktreePath,
13565
+ link: `sideboard://thread/${t.id}`
13566
+ }))
13567
+ },
13568
+ null,
13569
+ 2
13570
+ )
12977
13571
  }
12978
13572
  ]
12979
13573
  };
12980
- } catch (err) {
12981
- const message = err instanceof Error ? err.message : String(err);
12982
- return { content: [{ type: "text", text: message }], isError: true };
12983
13574
  }
12984
- }
12985
- );
12986
- server.tool(
12987
- "run_dev_script",
12988
- "Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
12989
- {
12990
- ref: import_zod.z.string(),
12991
- name: import_zod.z.string().optional()
12992
- },
12993
- async ({ ref, name }) => {
12994
- const result = await orch.startDev(ref, name);
12995
- return {
12996
- content: [
12997
- {
12998
- type: "text",
12999
- text: JSON.stringify({
13000
- port: result.port,
13001
- scriptName: result.scriptName,
13002
- ports: result.ports,
13003
- url: `http://localhost:${result.port}`
13004
- })
13005
- }
13006
- ]
13007
- };
13008
- }
13009
- );
13010
- server.tool(
13011
- "list_run_scripts",
13012
- "List named run scripts available for a thread",
13013
- { ref: import_zod.z.string() },
13014
- async ({ ref }) => {
13015
- const scripts = orch.listThreadRunScripts(ref);
13016
- const active = orch.getActiveRuns(ref);
13017
- return {
13018
- content: [
13019
- {
13020
- type: "text",
13021
- text: JSON.stringify({ scripts, active }, null, 2)
13022
- }
13023
- ]
13024
- };
13025
- }
13026
- );
13027
- server.tool(
13028
- "stop_dev_script",
13029
- "Stop a running script for a thread (all scripts if name omitted)",
13030
- {
13031
- ref: import_zod.z.string(),
13032
- name: import_zod.z.string().optional()
13033
- },
13034
- async ({ ref, name }) => {
13035
- orch.stopDev(ref, name);
13036
- return { content: [{ type: "text", text: "ok" }] };
13037
- }
13038
- );
13039
- server.tool(
13040
- "run_setup",
13041
- "Re-run workspace setup (Sideboard/Conductor settings or .cursor/worktrees.json)",
13042
- { ref: import_zod.z.string() },
13043
- async ({ ref }) => {
13044
- const result = await orch.runSetup(ref);
13045
- return {
13046
- content: [{ type: "text", text: JSON.stringify(result) }]
13047
- };
13048
- }
13049
- );
13050
- server.tool(
13051
- "add_workspace",
13052
- "Register a git repo as a Sideboard workspace",
13053
- { repoPath: import_zod.z.string() },
13054
- async ({ repoPath }) => {
13055
- const ws = await orch.addWorkspace(repoPath);
13056
- return { content: [{ type: "text", text: JSON.stringify(ws) }] };
13057
- }
13058
- );
13059
- server.tool(
13060
- "remove_workspace",
13061
- "Unregister a Sideboard workspace (does not archive threads)",
13062
- { repoPath: import_zod.z.string() },
13063
- async ({ repoPath }) => {
13064
- orch.removeWorkspace(repoPath);
13065
- return { content: [{ type: "text", text: "ok" }] };
13066
- }
13067
- );
13068
- server.tool(
13069
- "fanout",
13070
- "Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
13071
- {
13072
- prompt: import_zod.z.string(),
13073
- agents: import_zod.z.array(import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
13074
- repoPath: import_zod.z.string(),
13075
- sourceType: import_zod.z.enum(["branch", "pr", "ticket"]).optional(),
13076
- sourceRef: import_zod.z.string().optional(),
13077
- title: import_zod.z.string().optional()
13078
- },
13079
- async (args) => {
13080
- const threads = await orch.bestOfN(args);
13081
- return {
13082
- content: [
13083
- {
13084
- type: "text",
13085
- text: JSON.stringify(
13086
- threads.map((t) => ({
13087
- id: t.id,
13088
- agent: t.agent,
13089
- branchName: t.branchName,
13090
- worktreePath: t.worktreePath
13091
- })),
13092
- null,
13093
- 2
13094
- )
13095
- }
13096
- ]
13097
- };
13098
- }
13099
- );
13100
- server.tool(
13101
- "list_branches",
13102
- "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).",
13103
- {
13104
- repoPath: import_zod.z.string(),
13105
- unmergedOnly: import_zod.z.boolean().optional()
13106
- },
13107
- async ({ repoPath, unmergedOnly }) => {
13108
- const root = await resolveRepoRoot(repoPath);
13109
- const branches = await listBranches(root, {
13110
- unmergedOnly: unmergedOnly !== false
13111
- });
13112
- return {
13113
- content: [
13114
- {
13115
- type: "text",
13116
- text: branches.map((b) => `${b.current ? "*" : " "} ${b.name}`).join("\n")
13117
- }
13118
- ]
13119
- };
13120
- }
13121
- );
13122
- server.tool(
13123
- "list_prs",
13124
- "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.",
13125
- { repoPath: import_zod.z.string() },
13126
- async ({ repoPath }) => {
13127
- const root = await resolveRepoRoot(repoPath);
13128
- const prs = await listPrs(root);
13129
- return {
13130
- content: [
13131
- {
13132
- type: "text",
13133
- text: prs.map(
13134
- (p) => `#${p.number} ${p.title} (${p.headRefName})${p.isCrossRepository ? " [fork]" : ""}`
13135
- ).join("\n")
13136
- }
13137
- ]
13138
- };
13139
- }
13140
- );
13141
- server.tool(
13142
- "get_pr_stack",
13143
- "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.",
13144
- { ref: import_zod.z.string() },
13145
- async ({ ref }) => {
13146
- const stack = await orch.getPrStack(ref);
13147
- return {
13148
- content: [{ type: "text", text: JSON.stringify(stack, null, 2) }]
13149
- };
13150
- }
13151
- );
13152
- server.tool(
13153
- "open_pr_stack_layers",
13154
- "Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
13155
- {
13156
- ref: import_zod.z.string(),
13157
- layer: import_zod.z.number().int().positive().optional()
13158
- },
13159
- async ({ ref, layer }) => {
13160
- const result = await orch.openPrStackLayers(ref, { layer });
13161
- return {
13162
- content: [
13163
- {
13164
- type: "text",
13165
- text: JSON.stringify(
13166
- {
13167
- stackNumber: result.stack.stackNumber,
13168
- trunk: result.stack.trunk,
13169
- threads: result.threads.map((t) => ({
13170
- id: t.id,
13171
- title: t.title,
13172
- branchName: t.branchName,
13173
- stackLayer: t.stackLayer,
13174
- worktreePath: t.worktreePath,
13175
- prUrl: t.prUrl,
13176
- link: `sideboard://thread/${t.id}`
13177
- }))
13178
- },
13179
- null,
13180
- 2
13181
- )
13182
- }
13183
- ]
13184
- };
13185
- }
13186
- );
13187
- server.tool(
13188
- "add_stack_layer",
13189
- "Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
13190
- {
13191
- ref: import_zod.z.string(),
13192
- branchName: import_zod.z.string(),
13193
- title: import_zod.z.string().optional()
13194
- },
13195
- async ({ ref, branchName, title }) => {
13196
- const result = await orch.addStackLayer(ref, branchName, { title });
13197
- return {
13198
- content: [
13199
- {
13200
- type: "text",
13201
- text: JSON.stringify(
13202
- {
13203
- id: result.thread.id,
13204
- title: result.thread.title,
13205
- branchName: result.thread.branchName,
13206
- stackLayer: result.thread.stackLayer,
13207
- worktreePath: result.thread.worktreePath,
13208
- link: `sideboard://thread/${result.thread.id}`
13209
- },
13210
- null,
13211
- 2
13212
- )
13213
- }
13214
- ]
13215
- };
13216
- }
13217
- );
13218
- server.tool(
13219
- "create_pr_stack",
13220
- "Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
13221
- {
13222
- repoPath: import_zod.z.string(),
13223
- branches: import_zod.z.array(import_zod.z.string()).min(1),
13224
- agent: import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
13225
- base: import_zod.z.string().optional(),
13226
- title: import_zod.z.string().optional()
13227
- },
13228
- async (args) => {
13229
- const result = await orch.createPrStack({
13230
- repoPath: args.repoPath,
13231
- branches: args.branches,
13232
- agent: args.agent,
13233
- base: args.base,
13234
- title: args.title
13235
- });
13236
- return {
13237
- content: [
13238
- {
13239
- type: "text",
13240
- text: JSON.stringify(
13241
- {
13242
- stackNumber: result.stack.stackNumber,
13243
- trunk: result.stack.trunk,
13244
- threads: result.threads.map((t) => ({
13245
- id: t.id,
13246
- title: t.title,
13247
- branchName: t.branchName,
13248
- stackLayer: t.stackLayer,
13249
- worktreePath: t.worktreePath,
13250
- link: `sideboard://thread/${t.id}`
13251
- }))
13252
- },
13253
- null,
13254
- 2
13255
- )
13256
- }
13257
- ]
13258
- };
13259
- }
13260
- );
13261
- server.tool(
13262
- "list_issues",
13263
- "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.",
13264
- { repoPath: import_zod.z.string() },
13265
- async ({ repoPath }) => {
13266
- const root = await resolveRepoRoot(repoPath);
13267
- const result = await listIssues(root);
13268
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
13269
- }
13270
- );
13271
- server.tool(
13272
- "list_linear_issues",
13273
- "Deprecated: prefer list_issues. Lists assigned Linear issues via the chosen agent MCP connector",
13274
- {
13275
- agent: import_zod.z.enum(["claude", "codex", "opencode"]),
13276
- repoPath: import_zod.z.string()
13277
- },
13278
- async ({ agent, repoPath }) => {
13279
- const issues = await listLinearIssues(agent, repoPath);
13280
- return { content: [{ type: "text", text: JSON.stringify(issues, null, 2) }] };
13281
- }
13282
- );
13575
+ );
13576
+ server.tool(
13577
+ "list_issues",
13578
+ "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.",
13579
+ { repoPath: import_zod.z.string() },
13580
+ async ({ repoPath }) => {
13581
+ const root = await resolveRepoRoot(repoPath);
13582
+ const result = await listIssues(root);
13583
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
13584
+ }
13585
+ );
13586
+ server.tool(
13587
+ "list_linear_issues",
13588
+ "Deprecated: prefer list_issues. Lists assigned Linear issues via the chosen agent MCP connector",
13589
+ {
13590
+ agent: import_zod.z.enum(["claude", "codex", "opencode"]),
13591
+ repoPath: import_zod.z.string()
13592
+ },
13593
+ async ({ agent, repoPath }) => {
13594
+ const issues = await listLinearIssues(agent, repoPath);
13595
+ return { content: [{ type: "text", text: JSON.stringify(issues, null, 2) }] };
13596
+ }
13597
+ );
13598
+ }
13283
13599
  const transport = new import_stdio.StdioServerTransport();
13284
13600
  await server.connect(transport);
13285
13601
  }