@sideboard-ai/core 0.1.62 → 0.1.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/{agents-QLSFIQAO.js → agents-EWFD765A.js} +23 -7
  2. package/dist/{agents-KF3J3C53.js → agents-MD7SCFYX.js} +23 -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-RIWUU7FO.js → chunk-5AG5BUQN.js} +61 -3
  6. package/dist/{chunk-27MAWNKY.js → chunk-AKPEXIKK.js} +2 -2
  7. package/dist/{chunk-2MCNURQV.js → chunk-BBEH2CJE.js} +264 -93
  8. package/dist/{chunk-DDN3UF4R.js → chunk-BPOTVI5J.js} +115 -3
  9. package/dist/{chunk-2FEA4LDA.js → chunk-DNPP7QMK.js} +31 -6
  10. package/dist/{chunk-MBAL5FE6.js → chunk-DZYMNW2A.js} +4 -4
  11. package/dist/{chunk-JB2H7E7V.js → chunk-EVD4KNKR.js} +1 -1
  12. package/dist/{chunk-A6FOKNOU.js → chunk-HTOTTJJX.js} +2 -2
  13. package/dist/{chunk-3KKNTHUC.js → chunk-I2KGRAXC.js} +1 -1
  14. package/dist/{chunk-ZF5YJCXO.js → chunk-IFAU2DEY.js} +269 -93
  15. package/dist/{chunk-NVU7EC2E.js → chunk-LRBRQFBU.js} +2 -2
  16. package/dist/{chunk-6CUJQKTJ.js → chunk-MHR4OGZ4.js} +115 -3
  17. package/dist/{chunk-46KMKHSD.js → chunk-PESJDZTB.js} +2 -2
  18. package/dist/{chunk-UGXSSBFJ.js → chunk-R7GUDEPY.js} +1 -1
  19. package/dist/{chunk-73AT5K4A.js → chunk-TXQ6K2GR.js} +61 -3
  20. package/dist/{chunk-DXTGDXO2.js → chunk-YTNLOJPK.js} +31 -6
  21. package/dist/{connected-teams-RSL4VB3X.js → connected-teams-CHXO5FFA.js} +2 -2
  22. package/dist/{connected-teams-MKIYKOYA.js → connected-teams-GJTESSTX.js} +2 -2
  23. package/dist/{coordinator-prompt-LKG3SHMI.js → coordinator-prompt-E5UGSJES.js} +4 -4
  24. package/dist/{coordinator-prompt-N2R6FGZK.js → coordinator-prompt-ILSULI7C.js} +4 -4
  25. package/dist/{global-workspace-LALWQWRJ.js → global-workspace-44UG64NA.js} +5 -6
  26. package/dist/{global-workspace-YOAUQCMA.js → global-workspace-5PM5LA6E.js} +5 -6
  27. package/dist/index.cjs +1308 -925
  28. package/dist/index.d.cts +92 -1
  29. package/dist/index.d.ts +92 -1
  30. package/dist/index.js +746 -715
  31. package/dist/mcp/run-stdio.cjs +1267 -976
  32. package/dist/mcp/run-stdio.js +691 -748
  33. package/dist/{run-HZTBWGQR.js → run-74S2NZWB.js} +1 -1
  34. package/dist/{run-GPSVV5OH.js → run-JLN5JK7Q.js} +1 -1
  35. package/dist/{workspaces-VCAIHVIE.js → workspaces-OVBCSS5Y.js} +6 -7
  36. package/dist/{workspaces-E75POJXP.js → workspaces-RXFXQRRF.js} +6 -7
  37. package/dist/{worktree-MV4WRW6G.js → worktree-K2JB53R5.js} +2 -2
  38. package/dist/{worktree-LZEGVQSN.js → worktree-S2N63FLV.js} +2 -2
  39. package/package.json +1 -1
  40. package/dist/chunk-GI7E5733.js +0 -30
  41. package/dist/chunk-S2LL5HA4.js +0 -33
@@ -1507,18 +1507,28 @@ 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
+ }
1510
1520
  function ensureAgentPath(env = process.env) {
1511
1521
  const home = env.HOME || env.USERPROFILE || (0, import_node_os3.homedir)();
1512
1522
  const current = env.PATH ?? "";
1513
1523
  const parts = current.split(import_node_path4.delimiter).filter(Boolean);
1514
1524
  const seen = new Set(parts);
1515
1525
  const extras = [
1516
- ...EXTRA_BIN_DIRS.map((rel) => `${home}/${rel}`),
1526
+ ...EXTRA_BIN_DIRS.map((rel) => (0, import_node_path4.join)(home, rel)),
1517
1527
  "/opt/homebrew/bin",
1518
1528
  "/usr/local/bin"
1519
1529
  ];
1520
1530
  for (const dir of extras.reverse()) {
1521
- if (!dir || seen.has(dir)) continue;
1531
+ if (!dir || seen.has(dir) || !(0, import_node_fs4.existsSync)(dir)) continue;
1522
1532
  parts.unshift(dir);
1523
1533
  seen.add(dir);
1524
1534
  }
@@ -1526,10 +1536,50 @@ function ensureAgentPath(env = process.env) {
1526
1536
  env.PATH = next;
1527
1537
  return next;
1528
1538
  }
1529
- var import_node_os3, import_node_path4, EXTRA_BIN_DIRS;
1539
+ function enrichPathWithNpmGlobalBin(env = process.env) {
1540
+ ensureAgentPath(env);
1541
+ try {
1542
+ const prefix = (0, import_node_child_process.execFileSync)("npm", ["prefix", "-g"], {
1543
+ encoding: "utf8",
1544
+ env: { ...process.env, ...env },
1545
+ timeout: 8e3,
1546
+ stdio: ["ignore", "pipe", "ignore"]
1547
+ }).trim().split(/\r?\n/).find(Boolean);
1548
+ if (prefix) {
1549
+ const binDir = process.platform === "win32" ? prefix : (0, import_node_path4.join)(prefix, "bin");
1550
+ prependPathDir(env, binDir);
1551
+ }
1552
+ } catch {
1553
+ }
1554
+ return env.PATH ?? "";
1555
+ }
1556
+ function posixShellSingleQuote(value) {
1557
+ return `'${value.replace(/'/g, `'\\''`)}'`;
1558
+ }
1559
+ function resolveCommandBinarySync(command, whichPath) {
1560
+ const trimmed = command.trim();
1561
+ if (!trimmed) return trimmed;
1562
+ const match = /^(\S+)(\s[\s\S]*)?$/.exec(trimmed);
1563
+ if (!match) return trimmed;
1564
+ const bin = match[1];
1565
+ const rest = match[2] ?? "";
1566
+ if (bin.includes("/") || bin.includes("\\")) return trimmed;
1567
+ const abs = whichPath?.trim().split(/\r?\n/).find(Boolean);
1568
+ if (!abs) return trimmed;
1569
+ return `${abs}${rest}`;
1570
+ }
1571
+ function withExportedPath(command, pathValue) {
1572
+ const trimmed = command.trim();
1573
+ if (!trimmed) return trimmed;
1574
+ if (/^(export\s+PATH=|PATH=)/.test(trimmed)) return trimmed;
1575
+ return `export PATH=${posixShellSingleQuote(pathValue)}; ${trimmed}`;
1576
+ }
1577
+ var import_node_fs4, import_node_child_process, import_node_os3, import_node_path4, EXTRA_BIN_DIRS;
1530
1578
  var init_path = __esm({
1531
1579
  "src/agents/path.ts"() {
1532
1580
  "use strict";
1581
+ import_node_fs4 = require("fs");
1582
+ import_node_child_process = require("child_process");
1533
1583
  import_node_os3 = require("os");
1534
1584
  import_node_path4 = require("path");
1535
1585
  EXTRA_BIN_DIRS = [
@@ -1537,6 +1587,10 @@ var init_path = __esm({
1537
1587
  ".cargo/bin",
1538
1588
  ".nvm/current/bin",
1539
1589
  ".asdf/shims",
1590
+ ".volta/bin",
1591
+ ".npm-global/bin",
1592
+ // fnm default alias (common when shell init is skipped)
1593
+ ".local/share/fnm/aliases/default/bin",
1540
1594
  // pnpm / npm global bins (where `@brightsy/cli` typically lands)
1541
1595
  "Library/pnpm",
1542
1596
  ".pnpm"
@@ -2596,7 +2650,7 @@ function isLocalPrFetchBranch(ref) {
2596
2650
  async function createThreadWorktree(opts) {
2597
2651
  let branchName = `thread/${opts.slug}`;
2598
2652
  const worktreePath = (0, import_node_path5.join)(worktreesRoot(opts.repoPath), opts.slug);
2599
- if ((0, import_node_fs4.existsSync)(worktreePath)) {
2653
+ if ((0, import_node_fs5.existsSync)(worktreePath)) {
2600
2654
  throw new Error(`Worktree already exists at ${worktreePath}`);
2601
2655
  }
2602
2656
  await ensureGhPreferOrigin(opts.repoPath);
@@ -2666,7 +2720,7 @@ async function createExistingBranchWorktree(opts) {
2666
2720
  const branchName = opts.branchName.trim();
2667
2721
  if (!branchName) throw new Error("branch name required");
2668
2722
  const worktreePath = (0, import_node_path5.join)(worktreesRoot(opts.repoPath), opts.slug);
2669
- if ((0, import_node_fs4.existsSync)(worktreePath)) {
2723
+ if ((0, import_node_fs5.existsSync)(worktreePath)) {
2670
2724
  throw new Error(`Worktree already exists at ${worktreePath}`);
2671
2725
  }
2672
2726
  await ensureGhPreferOrigin(opts.repoPath);
@@ -2927,9 +2981,9 @@ function sameRepoPath(a, b) {
2927
2981
  }
2928
2982
  function listLocalThreadBranchSlugs(repoPath) {
2929
2983
  const refsDir = (0, import_node_path5.join)(repoPath, ".git", "refs", "heads", "thread");
2930
- if (!(0, import_node_fs4.existsSync)(refsDir)) return [];
2984
+ if (!(0, import_node_fs5.existsSync)(refsDir)) return [];
2931
2985
  try {
2932
- return (0, import_node_fs4.readdirSync)(refsDir).filter((name) => !name.startsWith(".")).map((name) => normalizeTakenSlug(name));
2986
+ return (0, import_node_fs5.readdirSync)(refsDir).filter((name) => !name.startsWith(".")).map((name) => normalizeTakenSlug(name));
2933
2987
  } catch {
2934
2988
  return [];
2935
2989
  }
@@ -2937,8 +2991,8 @@ function listLocalThreadBranchSlugs(repoPath) {
2937
2991
  function collectTakenTeamSlugs(repoPath) {
2938
2992
  const taken = /* @__PURE__ */ new Set();
2939
2993
  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 })) {
2994
+ if ((0, import_node_fs5.existsSync)(root)) {
2995
+ for (const entry of (0, import_node_fs5.readdirSync)(root, { withFileTypes: true })) {
2942
2996
  if (entry.isDirectory() && entry.name !== ".DS_Store") {
2943
2997
  taken.add(normalizeTakenSlug(entry.name));
2944
2998
  }
@@ -2960,16 +3014,16 @@ function allocateTeamSlug(repoPath) {
2960
3014
  for (let attempt = 0; attempt < 32; attempt++) {
2961
3015
  const team = allocateTeamName(taken);
2962
3016
  const path = (0, import_node_path5.join)(worktreesRoot(repoPath), team.slug);
2963
- if (!(0, import_node_fs4.existsSync)(path)) return team;
3017
+ if (!(0, import_node_fs5.existsSync)(path)) return team;
2964
3018
  taken.add(team.slug);
2965
3019
  }
2966
3020
  throw new Error("No available soccer team worktree directories left");
2967
3021
  }
2968
- var import_node_fs4, import_node_path5;
3022
+ var import_node_fs5, import_node_path5;
2969
3023
  var init_worktree = __esm({
2970
3024
  "src/git/worktree.ts"() {
2971
3025
  "use strict";
2972
- import_node_fs4 = require("fs");
3026
+ import_node_fs5 = require("fs");
2973
3027
  import_node_path5 = require("path");
2974
3028
  init_paths();
2975
3029
  init_thread_store();
@@ -2997,6 +3051,7 @@ __export(app_settings_exports, {
2997
3051
  autoRunAfterSetupEnabled: () => autoRunAfterSetupEnabled,
2998
3052
  brightsyCloudConnectAgent: () => brightsyCloudConnectAgent,
2999
3053
  brightsyCloudConnectEnabled: () => brightsyCloudConnectEnabled,
3054
+ brightsyInjectWorktreeMcpEnabled: () => brightsyInjectWorktreeMcpEnabled,
3000
3055
  caffeinateWhileCloudConnectEnabled: () => caffeinateWhileCloudConnectEnabled,
3001
3056
  caffeinateWhileRunningEnabled: () => caffeinateWhileRunningEnabled,
3002
3057
  childEnvWithAppSettings: () => childEnvWithAppSettings,
@@ -3015,17 +3070,21 @@ __export(app_settings_exports, {
3015
3070
  maxConcurrentAgents: () => maxConcurrentAgents,
3016
3071
  orchestrationQuotaFallbackAgent: () => orchestrationQuotaFallbackAgent,
3017
3072
  orchestrationQuotaOnLimit: () => orchestrationQuotaOnLimit,
3073
+ resolveAgentExecutable: () => resolveAgentExecutable,
3018
3074
  resolveClaudeExecutable: () => resolveClaudeExecutable,
3019
3075
  resolveEffectiveIssueSource: () => resolveEffectiveIssueSource,
3020
3076
  resolveNewThreadOptions: () => resolveNewThreadOptions,
3021
3077
  resolveThreadDefaults: () => resolveThreadDefaults,
3022
3078
  saveAppSettings: () => saveAppSettings,
3023
3079
  updateAdvancedSettings: () => updateAdvancedSettings,
3080
+ updateAgentExecutable: () => updateAgentExecutable,
3024
3081
  updateAppEnvironment: () => updateAppEnvironment,
3025
3082
  updateBrightsySettings: () => updateBrightsySettings,
3026
3083
  updateClaudeSettings: () => updateClaudeSettings,
3084
+ updateCodexSettings: () => updateCodexSettings,
3027
3085
  updateDefaultsSettings: () => updateDefaultsSettings,
3028
- updateIntegrationsSettings: () => updateIntegrationsSettings
3086
+ updateIntegrationsSettings: () => updateIntegrationsSettings,
3087
+ updateOpencodeSettings: () => updateOpencodeSettings
3029
3088
  });
3030
3089
  function appSettingsPath() {
3031
3090
  return (0, import_node_path6.join)(appDataDir(), "settings.json");
@@ -3046,16 +3105,33 @@ function normalizeClaude(raw) {
3046
3105
  }
3047
3106
  return out;
3048
3107
  }
3108
+ function normalizeCliExecutable(raw) {
3109
+ if (!raw || typeof raw !== "object") return {};
3110
+ const source = raw;
3111
+ const out = {};
3112
+ if (typeof source.executablePath === "string") {
3113
+ const path = source.executablePath.trim();
3114
+ if (path) out.executablePath = path;
3115
+ }
3116
+ return out;
3117
+ }
3049
3118
  function normalizeBrightsy(raw) {
3050
3119
  if (!raw || typeof raw !== "object") return {};
3051
3120
  const source = raw;
3052
3121
  const out = {};
3122
+ if (typeof source.executablePath === "string") {
3123
+ const path = source.executablePath.trim();
3124
+ if (path) out.executablePath = path;
3125
+ }
3053
3126
  if (typeof source.cloudConnectEnabled === "boolean") {
3054
3127
  out.cloudConnectEnabled = source.cloudConnectEnabled;
3055
3128
  }
3056
3129
  if (typeof source.cloudConnectAgent === "string" && CLOUD_CONNECT_AGENTS.has(source.cloudConnectAgent)) {
3057
3130
  out.cloudConnectAgent = source.cloudConnectAgent;
3058
3131
  }
3132
+ if (typeof source.injectWorktreeMcp === "boolean") {
3133
+ out.injectWorktreeMcp = source.injectWorktreeMcp;
3134
+ }
3059
3135
  return out;
3060
3136
  }
3061
3137
  function normalizeIntegrations(raw) {
@@ -3141,6 +3217,8 @@ function normalizeAdvanced(raw) {
3141
3217
  function normalizeSettings(raw) {
3142
3218
  const env = {};
3143
3219
  let claude = {};
3220
+ let codex = {};
3221
+ let opencode = {};
3144
3222
  let brightsy = {};
3145
3223
  let integrations = {};
3146
3224
  let defaults = {};
@@ -3159,6 +3237,12 @@ function normalizeSettings(raw) {
3159
3237
  if ("claude" in raw) {
3160
3238
  claude = normalizeClaude(raw.claude);
3161
3239
  }
3240
+ if ("codex" in raw) {
3241
+ codex = normalizeCliExecutable(raw.codex);
3242
+ }
3243
+ if ("opencode" in raw) {
3244
+ opencode = normalizeCliExecutable(raw.opencode);
3245
+ }
3162
3246
  if ("brightsy" in raw) {
3163
3247
  brightsy = normalizeBrightsy(raw.brightsy);
3164
3248
  }
@@ -3174,15 +3258,24 @@ function normalizeSettings(raw) {
3174
3258
  advanced = normalizeAdvanced(raw.advanced);
3175
3259
  }
3176
3260
  }
3177
- return { environment: env, claude, brightsy, integrations, defaults, advanced };
3261
+ return {
3262
+ environment: env,
3263
+ claude,
3264
+ codex,
3265
+ opencode,
3266
+ brightsy,
3267
+ integrations,
3268
+ defaults,
3269
+ advanced
3270
+ };
3178
3271
  }
3179
3272
  function loadAppSettings() {
3180
3273
  const path = appSettingsPath();
3181
- if (!(0, import_node_fs5.existsSync)(path)) {
3274
+ if (!(0, import_node_fs6.existsSync)(path)) {
3182
3275
  return { ...EMPTY_SETTINGS };
3183
3276
  }
3184
3277
  try {
3185
- const parsed = JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
3278
+ const parsed = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
3186
3279
  return normalizeSettings(parsed);
3187
3280
  } catch {
3188
3281
  return { ...EMPTY_SETTINGS };
@@ -3190,8 +3283,8 @@ function loadAppSettings() {
3190
3283
  }
3191
3284
  function saveAppSettings(settings) {
3192
3285
  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)}
3286
+ (0, import_node_fs6.mkdirSync)(appDataDir(), { recursive: true });
3287
+ (0, import_node_fs6.writeFileSync)(appSettingsPath(), `${JSON.stringify(normalized, null, 2)}
3195
3288
  `, "utf8");
3196
3289
  return normalized;
3197
3290
  }
@@ -3227,6 +3320,13 @@ function updateClaudeSettings(patch) {
3227
3320
  function updateBrightsySettings(patch) {
3228
3321
  const current = loadAppSettings();
3229
3322
  const brightsy = { ...current.brightsy };
3323
+ if ("executablePath" in patch) {
3324
+ if (patch.executablePath == null || patch.executablePath.trim() === "") {
3325
+ delete brightsy.executablePath;
3326
+ } else {
3327
+ brightsy.executablePath = patch.executablePath.trim();
3328
+ }
3329
+ }
3230
3330
  if (typeof patch.cloudConnectEnabled === "boolean") {
3231
3331
  brightsy.cloudConnectEnabled = patch.cloudConnectEnabled;
3232
3332
  }
@@ -3237,8 +3337,35 @@ function updateBrightsySettings(patch) {
3237
3337
  brightsy.cloudConnectAgent = patch.cloudConnectAgent;
3238
3338
  }
3239
3339
  }
3340
+ if (typeof patch.injectWorktreeMcp === "boolean") {
3341
+ brightsy.injectWorktreeMcp = patch.injectWorktreeMcp;
3342
+ }
3240
3343
  return saveAppSettings({ ...current, brightsy });
3241
3344
  }
3345
+ function updateCodexSettings(patch) {
3346
+ const current = loadAppSettings();
3347
+ const codex = { ...current.codex };
3348
+ if ("executablePath" in patch) {
3349
+ if (patch.executablePath == null || patch.executablePath.trim() === "") {
3350
+ delete codex.executablePath;
3351
+ } else {
3352
+ codex.executablePath = patch.executablePath.trim();
3353
+ }
3354
+ }
3355
+ return saveAppSettings({ ...current, codex });
3356
+ }
3357
+ function updateOpencodeSettings(patch) {
3358
+ const current = loadAppSettings();
3359
+ const opencode = { ...current.opencode };
3360
+ if ("executablePath" in patch) {
3361
+ if (patch.executablePath == null || patch.executablePath.trim() === "") {
3362
+ delete opencode.executablePath;
3363
+ } else {
3364
+ opencode.executablePath = patch.executablePath.trim();
3365
+ }
3366
+ }
3367
+ return saveAppSettings({ ...current, opencode });
3368
+ }
3242
3369
  function updateIntegrationsSettings(patch) {
3243
3370
  const current = loadAppSettings();
3244
3371
  const integrations = { ...current.integrations };
@@ -3346,6 +3473,9 @@ function getLinearApiKey(settings = loadAppSettings()) {
3346
3473
  function brightsyCloudConnectEnabled(settings = loadAppSettings()) {
3347
3474
  return Boolean(settings.brightsy.cloudConnectEnabled);
3348
3475
  }
3476
+ function brightsyInjectWorktreeMcpEnabled(settings = loadAppSettings()) {
3477
+ return Boolean(settings.brightsy.injectWorktreeMcp);
3478
+ }
3349
3479
  function brightsyCloudConnectAgent(settings = loadAppSettings()) {
3350
3480
  const fallback = settings.brightsy.cloudConnectAgent && CLOUD_CONNECT_AGENTS.has(settings.brightsy.cloudConnectAgent) ? settings.brightsy.cloudConnectAgent : "claude";
3351
3481
  const preferred = getDefaultAgent(settings);
@@ -3440,8 +3570,36 @@ function maxConcurrentAgents(settings = loadAppSettings()) {
3440
3570
  return 3;
3441
3571
  }
3442
3572
  function resolveClaudeExecutable(settings = loadAppSettings()) {
3443
- const override = settings.claude.executablePath?.trim();
3444
- return override || "claude";
3573
+ return resolveAgentExecutable("claude", settings);
3574
+ }
3575
+ function resolveAgentExecutable(agent, settings = loadAppSettings()) {
3576
+ const fallback = DEFAULT_CLI_BIN[agent];
3577
+ if (agent === "claude") {
3578
+ const override2 = settings.claude.executablePath?.trim();
3579
+ return override2 || fallback;
3580
+ }
3581
+ if (agent === "codex") {
3582
+ const override2 = settings.codex.executablePath?.trim();
3583
+ return override2 || fallback;
3584
+ }
3585
+ if (agent === "opencode") {
3586
+ const override2 = settings.opencode.executablePath?.trim();
3587
+ return override2 || fallback;
3588
+ }
3589
+ const override = settings.brightsy.executablePath?.trim();
3590
+ return override || fallback;
3591
+ }
3592
+ function updateAgentExecutable(agent, executablePath) {
3593
+ if (agent === "claude") {
3594
+ return updateClaudeSettings({ executablePath });
3595
+ }
3596
+ if (agent === "codex") {
3597
+ return updateCodexSettings({ executablePath });
3598
+ }
3599
+ if (agent === "opencode") {
3600
+ return updateOpencodeSettings({ executablePath });
3601
+ }
3602
+ return updateBrightsySettings({ executablePath });
3445
3603
  }
3446
3604
  function claudeChromeEnabled(settings = loadAppSettings()) {
3447
3605
  return Boolean(settings.claude.chromeEnabled);
@@ -3469,11 +3627,11 @@ function childEnvWithAppSettings(extra) {
3469
3627
  function harnessEnvKey(harness) {
3470
3628
  return HARNESS_ENV_KEYS[harness];
3471
3629
  }
3472
- var import_node_fs5, import_node_os4, import_node_path6, HARNESS_ENV_KEYS, DEFAULT_AGENTS, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, EMPTY_SETTINGS;
3630
+ var import_node_fs6, import_node_os4, import_node_path6, HARNESS_ENV_KEYS, DEFAULT_AGENTS, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, EMPTY_SETTINGS, DEFAULT_CLI_BIN;
3473
3631
  var init_app_settings = __esm({
3474
3632
  "src/store/app-settings.ts"() {
3475
3633
  "use strict";
3476
- import_node_fs5 = require("fs");
3634
+ import_node_fs6 = require("fs");
3477
3635
  import_node_os4 = require("os");
3478
3636
  import_node_path6 = require("path");
3479
3637
  init_thinking_effort();
@@ -3502,11 +3660,19 @@ var init_app_settings = __esm({
3502
3660
  EMPTY_SETTINGS = {
3503
3661
  environment: {},
3504
3662
  claude: {},
3663
+ codex: {},
3664
+ opencode: {},
3505
3665
  brightsy: {},
3506
3666
  integrations: {},
3507
3667
  defaults: {},
3508
3668
  advanced: {}
3509
3669
  };
3670
+ DEFAULT_CLI_BIN = {
3671
+ claude: "claude",
3672
+ codex: "codex",
3673
+ opencode: "opencode",
3674
+ brightsy: "brightsy"
3675
+ };
3510
3676
  }
3511
3677
  });
3512
3678
 
@@ -3632,7 +3798,7 @@ function coordinatorTurnReminder(opts) {
3632
3798
  function ensureGlobalCoordinatorCwd(opts) {
3633
3799
  const dir = globalAgentCwd();
3634
3800
  try {
3635
- (0, import_node_fs6.mkdirSync)(dir, { recursive: true });
3801
+ (0, import_node_fs7.mkdirSync)(dir, { recursive: true });
3636
3802
  } catch {
3637
3803
  return dir;
3638
3804
  }
@@ -3640,7 +3806,7 @@ function ensureGlobalCoordinatorCwd(opts) {
3640
3806
  let orchId = opts?.orchestratorThreadId?.trim() || "";
3641
3807
  if (!orchId) {
3642
3808
  try {
3643
- const existing = (0, import_node_fs6.readFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), "utf8");
3809
+ const existing = (0, import_node_fs7.readFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), "utf8");
3644
3810
  const m = existing.match(
3645
3811
  /YOUR orchestration thread id is `([0-9a-f-]{36})`/i
3646
3812
  );
@@ -3682,9 +3848,9 @@ function ensureGlobalCoordinatorCwd(opts) {
3682
3848
  "Always ask worktree agents to open draft PRs (`send_to_thread` + `gh pr create --draft -R <origin>`); never open PRs from the orchestrator."
3683
3849
  ].join("\n");
3684
3850
  try {
3685
- (0, import_node_fs6.writeFileSync)((0, import_node_path7.join)(dir, "CLAUDE.md"), `${body}
3851
+ (0, import_node_fs7.writeFileSync)((0, import_node_path7.join)(dir, "CLAUDE.md"), `${body}
3686
3852
  `, "utf8");
3687
- (0, import_node_fs6.writeFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), `${body}
3853
+ (0, import_node_fs7.writeFileSync)((0, import_node_path7.join)(dir, "AGENTS.md"), `${body}
3688
3854
  `, "utf8");
3689
3855
  } catch {
3690
3856
  }
@@ -3717,11 +3883,11 @@ function coordinatorSystemPrompt(opts) {
3717
3883
  formatWorkspaceInventory(opts.workspaces)
3718
3884
  ].join("\n");
3719
3885
  }
3720
- var import_node_fs6, import_node_path7, COORDINATOR_TOOL_PLAYBOOK;
3886
+ var import_node_fs7, import_node_path7, COORDINATOR_TOOL_PLAYBOOK;
3721
3887
  var init_coordinator_prompt = __esm({
3722
3888
  "src/orchestrator/coordinator-prompt.ts"() {
3723
3889
  "use strict";
3724
- import_node_fs6 = require("fs");
3890
+ import_node_fs7 = require("fs");
3725
3891
  import_node_path7 = require("path");
3726
3892
  init_worktree();
3727
3893
  init_app_settings();
@@ -3947,26 +4113,26 @@ function brightsyConfigPath() {
3947
4113
  }
3948
4114
  function loadBrightsyConfig() {
3949
4115
  const path = brightsyConfigPath();
3950
- if (!(0, import_node_fs7.existsSync)(path)) {
4116
+ if (!(0, import_node_fs8.existsSync)(path)) {
3951
4117
  throw new Error("Brightsy not logged in \u2014 run `brightsy login` first");
3952
4118
  }
3953
- const raw = JSON.parse((0, import_node_fs7.readFileSync)(path, "utf8"));
4119
+ const raw = JSON.parse((0, import_node_fs8.readFileSync)(path, "utf8"));
3954
4120
  if (!raw.access_token || !raw.account_id) {
3955
4121
  throw new Error("Brightsy config incomplete \u2014 run `brightsy login`");
3956
4122
  }
3957
4123
  return raw;
3958
4124
  }
3959
4125
  function saveBrightsyConfig(cfg) {
3960
- (0, import_node_fs7.writeFileSync)(brightsyConfigPath(), `${JSON.stringify(cfg, null, 2)}
4126
+ (0, import_node_fs8.writeFileSync)(brightsyConfigPath(), `${JSON.stringify(cfg, null, 2)}
3961
4127
  `, {
3962
4128
  mode: 384
3963
4129
  });
3964
4130
  }
3965
- var import_node_fs7, import_node_os5, import_node_path8;
4131
+ var import_node_fs8, import_node_os5, import_node_path8;
3966
4132
  var init_config = __esm({
3967
4133
  "src/brightsy/config.ts"() {
3968
4134
  "use strict";
3969
- import_node_fs7 = require("fs");
4135
+ import_node_fs8 = require("fs");
3970
4136
  import_node_os5 = require("os");
3971
4137
  import_node_path8 = require("path");
3972
4138
  }
@@ -3987,18 +4153,18 @@ function storePath() {
3987
4153
  }
3988
4154
  function readStore() {
3989
4155
  const path = storePath();
3990
- if (!(0, import_node_fs8.existsSync)(path)) return [];
4156
+ if (!(0, import_node_fs9.existsSync)(path)) return [];
3991
4157
  try {
3992
- const parsed = JSON.parse((0, import_node_fs8.readFileSync)(path, "utf8"));
4158
+ const parsed = JSON.parse((0, import_node_fs9.readFileSync)(path, "utf8"));
3993
4159
  return Array.isArray(parsed.teams) ? parsed.teams : [];
3994
4160
  } catch {
3995
4161
  return [];
3996
4162
  }
3997
4163
  }
3998
4164
  function writeStore(teams) {
3999
- (0, import_node_fs8.mkdirSync)(appDataDir(), { recursive: true });
4165
+ (0, import_node_fs9.mkdirSync)(appDataDir(), { recursive: true });
4000
4166
  const path = storePath();
4001
- (0, import_node_fs8.writeFileSync)(path, `${JSON.stringify({ teams }, null, 2)}
4167
+ (0, import_node_fs9.writeFileSync)(path, `${JSON.stringify({ teams }, null, 2)}
4002
4168
  `, {
4003
4169
  mode: 384
4004
4170
  });
@@ -4110,11 +4276,11 @@ function brightsyMcpServerName(slug) {
4110
4276
  const cleaned = slug.replace(/[^A-Za-z0-9_-]/g, "_").replace(/^_+|_+$/g, "");
4111
4277
  return `brightsy_${cleaned || "team"}`;
4112
4278
  }
4113
- var import_node_fs8, import_node_path9;
4279
+ var import_node_fs9, import_node_path9;
4114
4280
  var init_connected_teams = __esm({
4115
4281
  "src/brightsy/connected-teams.ts"() {
4116
4282
  "use strict";
4117
- import_node_fs8 = require("fs");
4283
+ import_node_fs9 = require("fs");
4118
4284
  import_node_path9 = require("path");
4119
4285
  init_paths();
4120
4286
  init_accounts();
@@ -4162,6 +4328,11 @@ function normalizeTurnInput(input) {
4162
4328
  if (typeof input === "string") return { prompt: input };
4163
4329
  return { prompt: input.prompt, cachedPrefix: input.cachedPrefix?.trim() || void 0 };
4164
4330
  }
4331
+ function dropCachedPrefixOnResume(input, sessionId) {
4332
+ const turn = normalizeTurnInput(input);
4333
+ if (sessionId) return { prompt: turn.prompt };
4334
+ return turn;
4335
+ }
4165
4336
  function flattenTurnInput(input) {
4166
4337
  const { cachedPrefix, prompt } = normalizeTurnInput(input);
4167
4338
  if (!cachedPrefix) return prompt;
@@ -4306,9 +4477,13 @@ async function fetchTeamChatTargets(team) {
4306
4477
  };
4307
4478
  }
4308
4479
  async function listBrightsyChatTargetsViaCli() {
4309
- const listed = await run("brightsy", ["chat", "--list-targets", "--json"], {
4310
- reject: false
4311
- });
4480
+ const listed = await run(
4481
+ resolveAgentExecutable("brightsy"),
4482
+ ["chat", "--list-targets", "--json"],
4483
+ {
4484
+ reject: false
4485
+ }
4486
+ );
4312
4487
  if (listed.exitCode !== 0 || !listed.stdout.trim()) {
4313
4488
  throw new Error(
4314
4489
  listed.stderr.trim() || "Failed to list Brightsy chat targets \u2014 is `brightsy` installed and logged in?"
@@ -4392,13 +4567,15 @@ async function syncCliForTarget(accountId) {
4392
4567
  }
4393
4568
  applyConnectedTeamToCli(team);
4394
4569
  }
4395
- var brightsyAdapter;
4570
+ var import_node_fs10, brightsyAdapter;
4396
4571
  var init_brightsy = __esm({
4397
4572
  "src/agents/brightsy.ts"() {
4398
4573
  "use strict";
4574
+ import_node_fs10 = require("fs");
4399
4575
  init_run();
4400
4576
  init_connected_teams();
4401
4577
  init_config();
4578
+ init_app_settings();
4402
4579
  init_brightsy_targets();
4403
4580
  init_error_detail();
4404
4581
  init_turn_input();
@@ -4406,18 +4583,32 @@ var init_brightsy = __esm({
4406
4583
  brightsyAdapter = {
4407
4584
  kind: "brightsy",
4408
4585
  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
- };
4586
+ const brightsy = resolveAgentExecutable("brightsy");
4587
+ if (brightsy !== "brightsy") {
4588
+ if (!(0, import_node_fs10.existsSync)(brightsy)) {
4589
+ return {
4590
+ agent: "brightsy",
4591
+ installed: false,
4592
+ authenticated: false,
4593
+ linearMcp: false,
4594
+ warnings: [],
4595
+ reason: `Brightsy executable not found: ${brightsy}`
4596
+ };
4597
+ }
4598
+ } else {
4599
+ const which = await run("which", ["brightsy"], { reject: false });
4600
+ if (which.exitCode !== 0) {
4601
+ return {
4602
+ agent: "brightsy",
4603
+ installed: false,
4604
+ authenticated: false,
4605
+ linearMcp: false,
4606
+ warnings: [],
4607
+ reason: "brightsy CLI not found on PATH \u2014 npm i -g @brightsy/cli"
4608
+ };
4609
+ }
4419
4610
  }
4420
- const who = await run("brightsy", ["whoami"], { reject: false });
4611
+ const who = await run(brightsy, ["whoami"], { reject: false });
4421
4612
  const authenticated = who.exitCode === 0 && /logged in as/i.test(who.stdout);
4422
4613
  return {
4423
4614
  agent: "brightsy",
@@ -4442,7 +4633,7 @@ var init_brightsy = __esm({
4442
4633
  target.id
4443
4634
  ];
4444
4635
  return {
4445
- file: "brightsy",
4636
+ file: resolveAgentExecutable("brightsy"),
4446
4637
  args,
4447
4638
  cwd: thread.worktreePath,
4448
4639
  // Piped stdin becomes the message body (avoids ARG_MAX for long seeds).
@@ -4459,7 +4650,11 @@ var init_brightsy = __esm({
4459
4650
  const target = decodeBrightsyTarget(thread.model);
4460
4651
  await syncCliForTarget(target.accountId);
4461
4652
  const args = target.type === "model" ? ["chat", "--model", target.id] : ["chat", "--agent", target.id];
4462
- return { file: "brightsy", args, cwd: thread.worktreePath };
4653
+ return {
4654
+ file: resolveAgentExecutable("brightsy"),
4655
+ args,
4656
+ cwd: thread.worktreePath
4657
+ };
4463
4658
  }
4464
4659
  };
4465
4660
  }
@@ -4504,6 +4699,18 @@ var init_claude_mcp = __esm({
4504
4699
  }
4505
4700
  });
4506
4701
 
4702
+ // src/mcp/profile.ts
4703
+ function sideboardMcpProfile(env = process.env) {
4704
+ return env[SIDEBOARD_MCP_PROFILE_ENV]?.trim().toLowerCase() === "worktree" ? "worktree" : "orchestration";
4705
+ }
4706
+ var SIDEBOARD_MCP_PROFILE_ENV;
4707
+ var init_profile = __esm({
4708
+ "src/mcp/profile.ts"() {
4709
+ "use strict";
4710
+ SIDEBOARD_MCP_PROFILE_ENV = "SIDEBOARD_MCP_PROFILE";
4711
+ }
4712
+ });
4713
+
4507
4714
  // src/agents/node-launch.ts
4508
4715
  function isAsarPath(filePath) {
4509
4716
  return /\.asar([/\\]|$)/.test(filePath);
@@ -4551,6 +4758,38 @@ function isBrightsyConnected() {
4551
4758
  return false;
4552
4759
  }
4553
4760
  }
4761
+ function promptMentionsBrightsy(text) {
4762
+ return BRIGHTSY_WORD.test(text ?? "");
4763
+ }
4764
+ function isBrightsyMcpToolName(name) {
4765
+ const n = name.toLowerCase();
4766
+ return n === "brightsy" || n.startsWith("mcp__brightsy") || n.startsWith("brightsy_");
4767
+ }
4768
+ function toolInputUsesBrightsyDatasource(input) {
4769
+ const ds = input?.datasource;
4770
+ return typeof ds === "string" && ds.toLowerCase() === "brightsy";
4771
+ }
4772
+ function messageUsedBrightsyMcp(msg) {
4773
+ if (msg.role === "user" && promptMentionsBrightsy(msg.text)) return true;
4774
+ for (const part of msg.parts ?? []) {
4775
+ if (part.type !== "tool") continue;
4776
+ if (isBrightsyMcpToolName(part.name)) return true;
4777
+ if (/present_(schema|files)$/i.test(part.name) && toolInputUsesBrightsyDatasource(part.input)) {
4778
+ return true;
4779
+ }
4780
+ }
4781
+ return false;
4782
+ }
4783
+ function threadRequestsBrightsyMcp(thread) {
4784
+ return (thread?.messages ?? []).some(messageUsedBrightsyMcp);
4785
+ }
4786
+ function shouldInjectBrightsyMcp(thread, opts) {
4787
+ if (!(opts?.connected ?? isBrightsyConnected())) return false;
4788
+ if (opts?.orchestrator) return true;
4789
+ const alwaysOn = opts?.alwaysOnWorktree ?? Boolean(loadAppSettings().brightsy?.injectWorktreeMcp);
4790
+ if (alwaysOn) return true;
4791
+ return threadRequestsBrightsyMcp(thread);
4792
+ }
4554
4793
  function mcpLaunch(cmd, name, env) {
4555
4794
  if (cmd === "brightsy-mcp") {
4556
4795
  return { name, command: "brightsy-mcp", ...env ? { env } : {} };
@@ -4596,7 +4835,7 @@ function corePackageDir() {
4596
4835
  }
4597
4836
  function findSideboardMcpJsEntry() {
4598
4837
  const override = process.env.SIDEBOARD_MCP_ENTRY?.trim() || process.env.SIDEBOARD_CLI?.trim();
4599
- if (override && (0, import_node_fs9.existsSync)(override)) return override;
4838
+ if (override && (0, import_node_fs11.existsSync)(override)) return override;
4600
4839
  let dir = corePackageDir();
4601
4840
  for (let i = 0; i < 10; i++) {
4602
4841
  const candidates = [
@@ -4609,7 +4848,7 @@ function findSideboardMcpJsEntry() {
4609
4848
  (0, import_node_path10.join)(dir, "cli/dist/index.js")
4610
4849
  ];
4611
4850
  for (const p of candidates) {
4612
- if ((0, import_node_fs9.existsSync)(p)) return p;
4851
+ if ((0, import_node_fs11.existsSync)(p)) return p;
4613
4852
  }
4614
4853
  const parent = (0, import_node_path10.dirname)(dir);
4615
4854
  if (parent === dir) break;
@@ -4640,11 +4879,13 @@ async function buildInjectedMcpServers(opts) {
4640
4879
  const servers = [];
4641
4880
  if (opts.includeSideboard) {
4642
4881
  const sideboard = await resolveSideboardMcpServer();
4882
+ const orchId = opts.orchestratorThreadId?.trim();
4883
+ const profile = orchId ? "orchestration" : "worktree";
4643
4884
  sideboard.env = {
4644
4885
  ...sideboard.env ?? {},
4645
- SIDEBOARD_APP_DATA: appDataDir()
4886
+ SIDEBOARD_APP_DATA: appDataDir(),
4887
+ [SIDEBOARD_MCP_PROFILE_ENV]: profile
4646
4888
  };
4647
- const orchId = opts.orchestratorThreadId?.trim();
4648
4889
  if (orchId) {
4649
4890
  sideboard.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID = orchId;
4650
4891
  }
@@ -4719,16 +4960,16 @@ function writeMcpServersConfig(servers) {
4719
4960
  ...s.env ? { env: s.env } : {}
4720
4961
  };
4721
4962
  }
4722
- const dir = (0, import_node_fs9.mkdtempSync)((0, import_node_path10.join)((0, import_node_os6.tmpdir)(), "sideboard-mcp-"));
4963
+ const dir = (0, import_node_fs11.mkdtempSync)((0, import_node_path10.join)((0, import_node_os6.tmpdir)(), "sideboard-mcp-"));
4723
4964
  const cfgPath = (0, import_node_path10.join)(dir, "mcp.json");
4724
- (0, import_node_fs9.writeFileSync)(cfgPath, JSON.stringify({ mcpServers }, null, 2));
4965
+ (0, import_node_fs11.writeFileSync)(cfgPath, JSON.stringify({ mcpServers }, null, 2));
4725
4966
  return cfgPath;
4726
4967
  }
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;
4968
+ var import_node_fs11, import_node_module, import_node_os6, import_node_path10, import_node_url, import_meta, SIDEBOARD_MCP_ALLOWED_TOOLS, SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS, brightsyMcpCommandCache, BRIGHTSY_WORD;
4728
4969
  var init_injected_mcp = __esm({
4729
4970
  "src/agents/injected-mcp.ts"() {
4730
4971
  "use strict";
4731
- import_node_fs9 = require("fs");
4972
+ import_node_fs11 = require("fs");
4732
4973
  import_node_module = require("module");
4733
4974
  import_node_os6 = require("os");
4734
4975
  import_node_path10 = require("path");
@@ -4736,6 +4977,8 @@ var init_injected_mcp = __esm({
4736
4977
  init_run();
4737
4978
  init_config();
4738
4979
  init_connected_teams();
4980
+ init_profile();
4981
+ init_app_settings();
4739
4982
  init_paths();
4740
4983
  init_node_launch();
4741
4984
  import_meta = {};
@@ -4751,6 +4994,7 @@ var init_injected_mcp = __esm({
4751
4994
  "mcp__sideboard__present_plan"
4752
4995
  ];
4753
4996
  brightsyMcpCommandCache = null;
4997
+ BRIGHTSY_WORD = /(?<![a-z0-9_-])brightsy(?![a-z0-9_-])/i;
4754
4998
  }
4755
4999
  });
4756
5000
 
@@ -4905,11 +5149,11 @@ function parseIssuesJson(raw) {
4905
5149
  }
4906
5150
  return [];
4907
5151
  }
4908
- var import_node_fs10, BASE_ALLOWED_TOOLS, CLAUDE_CHROME_ALLOWED_TOOLS, CLAUDE_PROMPT_ARG_MAX, claudeAdapter;
5152
+ var import_node_fs12, BASE_ALLOWED_TOOLS, CLAUDE_CHROME_ALLOWED_TOOLS, CLAUDE_PROMPT_ARG_MAX, claudeAdapter;
4909
5153
  var init_claude = __esm({
4910
5154
  "src/agents/claude.ts"() {
4911
5155
  "use strict";
4912
- import_node_fs10 = require("fs");
5156
+ import_node_fs12 = require("fs");
4913
5157
  init_run();
4914
5158
  init_app_settings();
4915
5159
  init_claude_mcp();
@@ -4938,7 +5182,7 @@ var init_claude = __esm({
4938
5182
  async detect() {
4939
5183
  const claude = resolveClaudeExecutable();
4940
5184
  if (claude !== "claude") {
4941
- if (!(0, import_node_fs10.existsSync)(claude)) {
5185
+ if (!(0, import_node_fs12.existsSync)(claude)) {
4942
5186
  return {
4943
5187
  agent: "claude",
4944
5188
  installed: false,
@@ -4977,9 +5221,8 @@ var init_claude = __esm({
4977
5221
  },
4978
5222
  async buildTurn(thread, input) {
4979
5223
  const claude = resolveClaudeExecutable();
4980
- const turn = normalizeTurnInput(input);
4981
5224
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
4982
- const effective = sessionId ? { prompt: turn.prompt } : turn;
5225
+ const effective = dropCachedPrefixOnResume(input, sessionId);
4983
5226
  const promptText = flattenTurnInput(effective);
4984
5227
  const useStdin = promptText.length > CLAUDE_PROMPT_ARG_MAX;
4985
5228
  if (process.env.SIDEBOARD_DEBUG_CLAUDE_TURN === "1") {
@@ -4992,7 +5235,9 @@ var init_claude = __esm({
4992
5235
  const isOrchestrator = isOrchestratorThread2(thread);
4993
5236
  const injectedServers = await buildInjectedMcpServers({
4994
5237
  includeSideboard: true,
4995
- includeBrightsy: isBrightsyConnected(),
5238
+ includeBrightsy: shouldInjectBrightsyMcp(thread, {
5239
+ orchestrator: isOrchestrator
5240
+ }),
4996
5241
  orchestratorThreadId: isOrchestrator ? thread.id : null
4997
5242
  });
4998
5243
  const injectedBrightsyNames = injectedServers.filter((s) => s.name === "brightsy" || s.name.startsWith("brightsy_")).map((s) => s.name);
@@ -5166,9 +5411,14 @@ async function listCodexModels() {
5166
5411
  if (cachedCodexModels && now - cachedCodexModels.at < CODEX_MODEL_CACHE_MS) {
5167
5412
  return cachedCodexModels.models;
5168
5413
  }
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 });
5414
+ const codex = resolveAgentExecutable("codex");
5415
+ if (codex === "codex") {
5416
+ const which = await run("which", ["codex"], { reject: false });
5417
+ if (which.exitCode !== 0) return FALLBACK_CODEX_MODELS;
5418
+ } else if (!(0, import_node_fs13.existsSync)(codex)) {
5419
+ return FALLBACK_CODEX_MODELS;
5420
+ }
5421
+ const listed = await run(codex, ["debug", "models"], { reject: false });
5172
5422
  if (listed.exitCode !== 0 || !listed.stdout.trim()) {
5173
5423
  return cachedCodexModels?.models ?? FALLBACK_CODEX_MODELS;
5174
5424
  }
@@ -5207,8 +5457,8 @@ function codexConfigHasNetworkAccess() {
5207
5457
  (0, import_node_path11.join)((0, import_node_os7.homedir)(), ".config", "codex", "config.toml")
5208
5458
  ];
5209
5459
  for (const path of candidates) {
5210
- if (!(0, import_node_fs11.existsSync)(path)) continue;
5211
- const text = (0, import_node_fs11.readFileSync)(path, "utf8");
5460
+ if (!(0, import_node_fs13.existsSync)(path)) continue;
5461
+ const text = (0, import_node_fs13.readFileSync)(path, "utf8");
5212
5462
  if (/network_access\s*=\s*true/.test(text)) return true;
5213
5463
  }
5214
5464
  return false;
@@ -5241,21 +5491,23 @@ function asRecord(value) {
5241
5491
  }
5242
5492
  function codexLooksAuthenticated() {
5243
5493
  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;
5494
+ if (!(0, import_node_fs13.existsSync)(authPath)) return false;
5245
5495
  try {
5246
- return (0, import_node_fs11.statSync)(authPath).size > 2;
5496
+ return (0, import_node_fs13.statSync)(authPath).size > 2;
5247
5497
  } catch {
5248
5498
  return false;
5249
5499
  }
5250
5500
  }
5251
- var import_node_fs11, import_node_os7, import_node_path11, CODEX_PROMPT_ARG_MAX, FALLBACK_CODEX_MODELS, cachedCodexModels, CODEX_MODEL_CACHE_MS, codexAdapter;
5501
+ var import_node_fs13, import_node_os7, import_node_path11, CODEX_PROMPT_ARG_MAX, FALLBACK_CODEX_MODELS, cachedCodexModels, CODEX_MODEL_CACHE_MS, codexAdapter;
5252
5502
  var init_codex = __esm({
5253
5503
  "src/agents/codex.ts"() {
5254
5504
  "use strict";
5255
- import_node_fs11 = require("fs");
5505
+ import_node_fs13 = require("fs");
5256
5506
  import_node_os7 = require("os");
5257
5507
  import_node_path11 = require("path");
5258
5508
  init_run();
5509
+ init_app_settings();
5510
+ init_global_workspace();
5259
5511
  init_error_detail();
5260
5512
  init_injected_mcp();
5261
5513
  init_turn_input();
@@ -5273,16 +5525,30 @@ var init_codex = __esm({
5273
5525
  codexAdapter = {
5274
5526
  kind: "codex",
5275
5527
  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
- };
5528
+ const codex = resolveAgentExecutable("codex");
5529
+ if (codex !== "codex") {
5530
+ if (!(0, import_node_fs13.existsSync)(codex)) {
5531
+ return {
5532
+ agent: "codex",
5533
+ installed: false,
5534
+ authenticated: false,
5535
+ linearMcp: false,
5536
+ warnings: [],
5537
+ reason: `Codex executable not found: ${codex}`
5538
+ };
5539
+ }
5540
+ } else {
5541
+ const which = await run("which", ["codex"], { reject: false, timeoutMs: 3e3 });
5542
+ if (which.exitCode !== 0) {
5543
+ return {
5544
+ agent: "codex",
5545
+ installed: false,
5546
+ authenticated: false,
5547
+ linearMcp: false,
5548
+ warnings: [],
5549
+ reason: "codex CLI not found on PATH"
5550
+ };
5551
+ }
5286
5552
  }
5287
5553
  const authenticated = codexLooksAuthenticated();
5288
5554
  const linearMcp = false;
@@ -5302,22 +5568,24 @@ var init_codex = __esm({
5302
5568
  };
5303
5569
  },
5304
5570
  async buildTurn(thread, input) {
5305
- const prompt = flattenTurnInput(input);
5306
5571
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
5572
+ const prompt = flattenTurnInput(dropCachedPrefixOnResume(input, sessionId));
5307
5573
  const useStdin = prompt.length > CODEX_PROMPT_ARG_MAX;
5308
5574
  const promptArg = useStdin ? "-" : prompt;
5309
5575
  if (process.env.SIDEBOARD_DEBUG_CODEX_TURN === "1") {
5310
- const { cachedPrefix } = normalizeTurnInput(input);
5311
5576
  console.error(
5312
- `[sideboard/codex] promptChars=${prompt.length} resumed=${Boolean(sessionId)} stdin=${useStdin} hasPrefix=${Boolean(cachedPrefix)}`
5577
+ `[sideboard/codex] promptChars=${prompt.length} resumed=${Boolean(sessionId)} stdin=${useStdin} hasPrefix=${prompt.includes("Current request:")}`
5313
5578
  );
5314
5579
  }
5315
5580
  const mode = permissionMode(thread);
5316
5581
  const model = thread.model?.trim();
5582
+ const isOrchestrator = isOrchestratorThread(thread);
5317
5583
  const injected = await buildInjectedMcpServers({
5318
5584
  includeSideboard: true,
5319
- includeBrightsy: isBrightsyConnected(),
5320
- orchestratorThreadId: thread.sourceType === "orchestration" ? thread.id : null
5585
+ includeBrightsy: shouldInjectBrightsyMcp(thread, {
5586
+ orchestrator: isOrchestrator
5587
+ }),
5588
+ orchestratorThreadId: isOrchestrator ? thread.id : null
5321
5589
  });
5322
5590
  const mcpOverrides = toCodexMcpConfigArgs(injected);
5323
5591
  const execOpts = [
@@ -5337,7 +5605,7 @@ var init_codex = __esm({
5337
5605
  ];
5338
5606
  const args = sessionId ? ["exec", ...execOpts, "resume", sessionId, promptArg] : ["exec", ...execOpts, promptArg];
5339
5607
  return {
5340
- file: "codex",
5608
+ file: resolveAgentExecutable("codex"),
5341
5609
  args,
5342
5610
  cwd: thread.worktreePath,
5343
5611
  stdin: useStdin ? `${prompt}
@@ -5433,16 +5701,17 @@ var init_codex = __esm({
5433
5701
  return cached;
5434
5702
  },
5435
5703
  async buildAttach(thread) {
5704
+ const codex = resolveAgentExecutable("codex");
5436
5705
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
5437
5706
  if (sessionId) {
5438
5707
  return {
5439
- file: "codex",
5708
+ file: codex,
5440
5709
  args: ["exec", "--cd", thread.worktreePath, "resume", sessionId],
5441
5710
  cwd: thread.worktreePath
5442
5711
  };
5443
5712
  }
5444
5713
  return {
5445
- file: "codex",
5714
+ file: codex,
5446
5715
  args: ["--cd", thread.worktreePath],
5447
5716
  cwd: thread.worktreePath
5448
5717
  };
@@ -5450,7 +5719,7 @@ var init_codex = __esm({
5450
5719
  async listLinearIssues(_repoPath) {
5451
5720
  const prompt = "List my assigned Linear issues as JSON array only: id, identifier, title, url, labels.";
5452
5721
  const { stdout, exitCode } = await run(
5453
- "codex",
5722
+ resolveAgentExecutable("codex"),
5454
5723
  [
5455
5724
  "exec",
5456
5725
  "--json",
@@ -5713,21 +5982,22 @@ function cursorRunnerPath() {
5713
5982
  (0, import_node_path12.join)(root, "src", "agents", "cursor-runner.ts")
5714
5983
  ];
5715
5984
  for (const candidate of candidates) {
5716
- if ((0, import_node_fs12.existsSync)(candidate)) return candidate;
5985
+ if ((0, import_node_fs14.existsSync)(candidate)) return candidate;
5717
5986
  }
5718
5987
  return candidates[0];
5719
5988
  }
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;
5989
+ var import_node_fs14, import_node_module2, import_node_path12, import_node_url2, import_sdk, import_meta2, FALLBACK_CURSOR_MODELS, cachedModels, MODEL_CACHE_MS, cursorAdapter;
5721
5990
  var init_cursor = __esm({
5722
5991
  "src/agents/cursor.ts"() {
5723
5992
  "use strict";
5724
- import_node_fs12 = require("fs");
5993
+ import_node_fs14 = require("fs");
5725
5994
  import_node_module2 = require("module");
5726
5995
  import_node_path12 = require("path");
5727
5996
  import_node_url2 = require("url");
5728
5997
  import_sdk = require("@cursor/sdk");
5729
5998
  init_run();
5730
5999
  init_app_settings();
6000
+ init_global_workspace();
5731
6001
  init_cursor_events();
5732
6002
  init_injected_mcp();
5733
6003
  init_node_launch();
@@ -5764,13 +6034,16 @@ var init_cursor = __esm({
5764
6034
  };
5765
6035
  },
5766
6036
  async buildTurn(thread, input) {
5767
- const prompt = flattenTurnInput(input);
5768
6037
  const agentId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
6038
+ const prompt = flattenTurnInput(dropCachedPrefixOnResume(input, agentId));
5769
6039
  const apiKey = resolveCursorApiKey() || void 0;
6040
+ const isOrchestrator = isOrchestratorThread(thread);
5770
6041
  const injected = await buildInjectedMcpServers({
5771
6042
  includeSideboard: true,
5772
- includeBrightsy: isBrightsyConnected(),
5773
- orchestratorThreadId: thread.sourceType === "orchestration" ? thread.id : null
6043
+ includeBrightsy: shouldInjectBrightsyMcp(thread, {
6044
+ orchestrator: isOrchestrator
6045
+ }),
6046
+ orchestratorThreadId: isOrchestrator ? thread.id : null
5774
6047
  });
5775
6048
  const mcpServers = toCursorMcpServers(injected);
5776
6049
  const req = {
@@ -5841,9 +6114,14 @@ async function listOpencodeModels() {
5841
6114
  if (cachedOpencodeModels && now - cachedOpencodeModels.at < OPENCODE_MODEL_CACHE_MS) {
5842
6115
  return cachedOpencodeModels.models;
5843
6116
  }
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 });
6117
+ const opencode = resolveAgentExecutable("opencode");
6118
+ if (opencode === "opencode") {
6119
+ const which = await run("which", ["opencode"], { reject: false });
6120
+ if (which.exitCode !== 0) return FALLBACK_OPENCODE_MODELS;
6121
+ } else if (!(0, import_node_fs15.existsSync)(opencode)) {
6122
+ return FALLBACK_OPENCODE_MODELS;
6123
+ }
6124
+ const listed = await run(opencode, ["models"], { reject: false });
5847
6125
  if (listed.exitCode !== 0 || !listed.stdout.trim()) {
5848
6126
  return cachedOpencodeModels?.models ?? FALLBACK_OPENCODE_MODELS;
5849
6127
  }
@@ -5870,11 +6148,14 @@ function usageFromOpencode(tokens) {
5870
6148
  cacheWriteTokens: tokens.cache?.write ? Number(tokens.cache.write) : void 0
5871
6149
  };
5872
6150
  }
5873
- var FALLBACK_OPENCODE_MODELS, cachedOpencodeModels, OPENCODE_MODEL_CACHE_MS, opencodeAdapter;
6151
+ var import_node_fs15, FALLBACK_OPENCODE_MODELS, cachedOpencodeModels, OPENCODE_MODEL_CACHE_MS, opencodeAdapter;
5874
6152
  var init_opencode = __esm({
5875
6153
  "src/agents/opencode.ts"() {
5876
6154
  "use strict";
6155
+ import_node_fs15 = require("fs");
5877
6156
  init_run();
6157
+ init_app_settings();
6158
+ init_global_workspace();
5878
6159
  init_error_detail();
5879
6160
  init_injected_mcp();
5880
6161
  init_turn_input();
@@ -5895,20 +6176,34 @@ var init_opencode = __esm({
5895
6176
  opencodeAdapter = {
5896
6177
  kind: "opencode",
5897
6178
  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
- };
6179
+ const opencode = resolveAgentExecutable("opencode");
6180
+ if (opencode !== "opencode") {
6181
+ if (!(0, import_node_fs15.existsSync)(opencode)) {
6182
+ return {
6183
+ agent: "opencode",
6184
+ installed: false,
6185
+ authenticated: false,
6186
+ linearMcp: false,
6187
+ warnings: [],
6188
+ reason: `OpenCode executable not found: ${opencode}`
6189
+ };
6190
+ }
6191
+ } else {
6192
+ const which = await run("which", ["opencode"], { reject: false });
6193
+ if (which.exitCode !== 0) {
6194
+ return {
6195
+ agent: "opencode",
6196
+ installed: false,
6197
+ authenticated: false,
6198
+ linearMcp: false,
6199
+ warnings: [],
6200
+ reason: "opencode CLI not found on PATH"
6201
+ };
6202
+ }
5908
6203
  }
5909
- const auth = await run("opencode", ["auth", "list"], { reject: false });
6204
+ const auth = await run(opencode, ["auth", "list"], { reject: false });
5910
6205
  const authenticated = auth.exitCode === 0 && auth.stdout.trim().length > 0;
5911
- const mcp = await run("opencode", ["mcp", "list"], { reject: false });
6206
+ const mcp = await run(opencode, ["mcp", "list"], { reject: false });
5912
6207
  const linearMcp = /linear/i.test(mcp.stdout + mcp.stderr);
5913
6208
  return {
5914
6209
  agent: "opencode",
@@ -5920,8 +6215,8 @@ var init_opencode = __esm({
5920
6215
  };
5921
6216
  },
5922
6217
  async buildTurn(thread, input) {
5923
- const prompt = flattenTurnInput(input);
5924
6218
  const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
6219
+ const prompt = flattenTurnInput(dropCachedPrefixOnResume(input, sessionId));
5925
6220
  const mode = permissionMode(thread);
5926
6221
  const model = thread.model?.trim();
5927
6222
  const args = [
@@ -5937,14 +6232,17 @@ var init_opencode = __esm({
5937
6232
  if (model) {
5938
6233
  args.push("--model", model);
5939
6234
  }
6235
+ const isOrchestrator = isOrchestratorThread(thread);
5940
6236
  const injected = await buildInjectedMcpServers({
5941
6237
  includeSideboard: true,
5942
- includeBrightsy: isBrightsyConnected(),
5943
- orchestratorThreadId: thread.sourceType === "orchestration" ? thread.id : null
6238
+ includeBrightsy: shouldInjectBrightsyMcp(thread, {
6239
+ orchestrator: isOrchestrator
6240
+ }),
6241
+ orchestratorThreadId: isOrchestrator ? thread.id : null
5944
6242
  });
5945
6243
  const mcpContent = injected.length > 0 ? toOpencodeMcpConfigContent(injected) : null;
5946
6244
  return {
5947
- file: "opencode",
6245
+ file: resolveAgentExecutable("opencode"),
5948
6246
  args,
5949
6247
  cwd: thread.worktreePath,
5950
6248
  // `opencode run` treats non-TTY stdin as the message body when no positional
@@ -6020,7 +6318,7 @@ var init_opencode = __esm({
6020
6318
  async resolveSessionId(worktreePath, cached) {
6021
6319
  const cachedId = cached?.trim() || null;
6022
6320
  const listed = await run(
6023
- "opencode",
6321
+ resolveAgentExecutable("opencode"),
6024
6322
  ["session", "list", "--format", "json"],
6025
6323
  { cwd: worktreePath, reject: false }
6026
6324
  );
@@ -6051,7 +6349,7 @@ var init_opencode = __esm({
6051
6349
  const args = ["--dir", thread.worktreePath];
6052
6350
  if (sessionId) args.push("--session", sessionId);
6053
6351
  return {
6054
- file: "opencode",
6352
+ file: resolveAgentExecutable("opencode"),
6055
6353
  args,
6056
6354
  cwd: thread.worktreePath,
6057
6355
  env: {
@@ -6062,7 +6360,7 @@ var init_opencode = __esm({
6062
6360
  async listLinearIssues(_repoPath) {
6063
6361
  const prompt = "List my assigned Linear issues as JSON array only: id, identifier, title, url, labels.";
6064
6362
  const { stdout, exitCode } = await run(
6065
- "opencode",
6363
+ resolveAgentExecutable("opencode"),
6066
6364
  ["run", prompt, "--format", "json"],
6067
6365
  {
6068
6366
  reject: false,
@@ -6295,9 +6593,30 @@ function getAgentSetupInfo(agent) {
6295
6593
  function listAgentSetupInfo() {
6296
6594
  return Object.values(SETUP);
6297
6595
  }
6298
- async function openInSystemTerminal(command) {
6299
- ensureAgentPath();
6596
+ function resolveLoginCommand(agent) {
6597
+ const info = getAgentSetupInfo(agent);
6598
+ if (!info.loginCommand) return null;
6599
+ if (!CLI_LOGIN_AGENTS.has(agent)) return info.loginCommand;
6600
+ const exe = resolveAgentExecutable(agent);
6601
+ return info.loginCommand.replace(/^\S+/, exe);
6602
+ }
6603
+ async function prepareTerminalCommand(command) {
6604
+ enrichPathWithNpmGlobalBin();
6300
6605
  const trimmed = command.trim();
6606
+ if (!trimmed) return trimmed;
6607
+ if (/^(export\s+PATH=|PATH=)/.test(trimmed)) return trimmed;
6608
+ const match = /^(\S+)(\s[\s\S]*)?$/.exec(trimmed);
6609
+ const bin = match?.[1];
6610
+ let whichPath = null;
6611
+ if (bin && !bin.includes("/") && !bin.includes("\\")) {
6612
+ const which = await run("which", [bin], { reject: false, timeoutMs: 3e3 });
6613
+ whichPath = which.exitCode === 0 ? which.stdout.trim() : null;
6614
+ }
6615
+ const resolved = resolveCommandBinarySync(trimmed, whichPath);
6616
+ return withExportedPath(resolved, process.env.PATH ?? "");
6617
+ }
6618
+ async function openInSystemTerminal(command) {
6619
+ const trimmed = (await prepareTerminalCommand(command)).trim();
6301
6620
  if (!trimmed) throw new Error("Command is empty");
6302
6621
  if (process.platform === "darwin") {
6303
6622
  const escaped = trimmed.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
@@ -6358,7 +6677,7 @@ async function installAgent(agent) {
6358
6677
  message: `Opened Terminal to run: ${info.installCommand}`
6359
6678
  };
6360
6679
  }
6361
- ensureAgentPath();
6680
+ enrichPathWithNpmGlobalBin();
6362
6681
  const result = await run("npm", ["install", "-g", info.npmPackage], { reject: false });
6363
6682
  const ok = result.exitCode === 0;
6364
6683
  if (!ok) {
@@ -6384,36 +6703,46 @@ async function installAgent(agent) {
6384
6703
  };
6385
6704
  }
6386
6705
  }
6706
+ enrichPathWithNpmGlobalBin();
6707
+ const cliBin = agent === "cursor" ? null : CLI_BIN[agent];
6708
+ let binPath = null;
6709
+ if (cliBin) {
6710
+ const which = await run("which", [cliBin], { reject: false, timeoutMs: 3e3 });
6711
+ binPath = which.exitCode === 0 ? which.stdout.trim().split(/\r?\n/).find(Boolean) ?? null : null;
6712
+ }
6387
6713
  return {
6388
6714
  ok: true,
6389
6715
  command: info.installCommand,
6390
6716
  exitCode: 0,
6391
6717
  stdout: result.stdout,
6392
6718
  stderr: result.stderr,
6393
- message: `Installed ${info.npmPackage}`
6719
+ message: binPath ? `Installed ${info.npmPackage} \u2192 ${binPath}` : `Installed ${info.npmPackage} (CLI not yet on PATH \u2014 Log in still exports Electron\u2019s PATH into Terminal)`
6394
6720
  };
6395
6721
  }
6396
6722
  async function loginAgent(agent) {
6397
6723
  const info = getAgentSetupInfo(agent);
6398
- if (!info.loginCommand) {
6724
+ const loginCommand = resolveLoginCommand(agent);
6725
+ if (!loginCommand) {
6399
6726
  return {
6400
6727
  ok: true,
6401
6728
  message: info.kind === "bundled-sdk" || info.kind === "api-key" ? info.summary : `No login command for ${agent}`
6402
6729
  };
6403
6730
  }
6404
- await openInSystemTerminal(info.loginCommand);
6731
+ const prepared = await prepareTerminalCommand(loginCommand);
6732
+ await openInSystemTerminal(prepared);
6405
6733
  return {
6406
6734
  ok: true,
6407
6735
  openedTerminal: true,
6408
- command: info.loginCommand,
6409
- message: `Opened Terminal to run: ${info.loginCommand}`
6736
+ command: prepared,
6737
+ message: `Opened Terminal to run: ${prepared}`
6410
6738
  };
6411
6739
  }
6412
- var SETUP;
6740
+ var SETUP, CLI_BIN, CLI_LOGIN_AGENTS;
6413
6741
  var init_install = __esm({
6414
6742
  "src/agents/install.ts"() {
6415
6743
  "use strict";
6416
6744
  init_run();
6745
+ init_app_settings();
6417
6746
  init_path();
6418
6747
  SETUP = {
6419
6748
  claude: {
@@ -6462,6 +6791,18 @@ var init_install = __esm({
6462
6791
  npmPackage: "@brightsy/cli"
6463
6792
  }
6464
6793
  };
6794
+ CLI_BIN = {
6795
+ claude: "claude",
6796
+ codex: "codex",
6797
+ opencode: "opencode",
6798
+ brightsy: "brightsy"
6799
+ };
6800
+ CLI_LOGIN_AGENTS = /* @__PURE__ */ new Set([
6801
+ "claude",
6802
+ "codex",
6803
+ "opencode",
6804
+ "brightsy"
6805
+ ]);
6465
6806
  }
6466
6807
  });
6467
6808
 
@@ -6481,6 +6822,7 @@ __export(agents_exports, {
6481
6822
  cursorSdkMessageToEvents: () => cursorSdkMessageToEvents,
6482
6823
  decodeBrightsyTarget: () => decodeBrightsyTarget,
6483
6824
  encodeBrightsyTarget: () => encodeBrightsyTarget,
6825
+ enrichPathWithNpmGlobalBin: () => enrichPathWithNpmGlobalBin,
6484
6826
  ensureAgentPath: () => ensureAgentPath,
6485
6827
  getAdapter: () => getAdapter,
6486
6828
  getAgentSetupInfo: () => getAgentSetupInfo,
@@ -6500,8 +6842,13 @@ __export(agents_exports, {
6500
6842
  parseCursorRunnerLine: () => parseCursorRunnerLine,
6501
6843
  parseSessionQuotaResetAt: () => parseSessionQuotaResetAt,
6502
6844
  permissionMode: () => permissionMode,
6845
+ posixShellSingleQuote: () => posixShellSingleQuote,
6846
+ prepareTerminalCommand: () => prepareTerminalCommand,
6847
+ resolveCommandBinarySync: () => resolveCommandBinarySync,
6503
6848
  resolveCursorModelId: () => resolveCursorModelId,
6504
- resolveQuotaFallbackAgent: () => resolveQuotaFallbackAgent
6849
+ resolveLoginCommand: () => resolveLoginCommand,
6850
+ resolveQuotaFallbackAgent: () => resolveQuotaFallbackAgent,
6851
+ withExportedPath: () => withExportedPath
6505
6852
  });
6506
6853
  function getAdapter(kind) {
6507
6854
  return adapters[kind];
@@ -6559,31 +6906,31 @@ function removedWorkspacesFile() {
6559
6906
  }
6560
6907
  function readAll() {
6561
6908
  const path = workspacesFile();
6562
- if (!(0, import_node_fs16.existsSync)(path)) return [];
6909
+ if (!(0, import_node_fs19.existsSync)(path)) return [];
6563
6910
  try {
6564
- const raw = JSON.parse((0, import_node_fs16.readFileSync)(path, "utf8"));
6911
+ const raw = JSON.parse((0, import_node_fs19.readFileSync)(path, "utf8"));
6565
6912
  return Array.isArray(raw) ? raw : [];
6566
6913
  } catch {
6567
6914
  return [];
6568
6915
  }
6569
6916
  }
6570
6917
  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");
6918
+ (0, import_node_fs19.mkdirSync)(appDataDir(), { recursive: true });
6919
+ (0, import_node_fs19.writeFileSync)(workspacesFile(), JSON.stringify(list, null, 2), "utf8");
6573
6920
  }
6574
6921
  function readRemoved() {
6575
6922
  const path = removedWorkspacesFile();
6576
- if (!(0, import_node_fs16.existsSync)(path)) return /* @__PURE__ */ new Set();
6923
+ if (!(0, import_node_fs19.existsSync)(path)) return /* @__PURE__ */ new Set();
6577
6924
  try {
6578
- const raw = JSON.parse((0, import_node_fs16.readFileSync)(path, "utf8"));
6925
+ const raw = JSON.parse((0, import_node_fs19.readFileSync)(path, "utf8"));
6579
6926
  return new Set(Array.isArray(raw) ? raw.filter((p) => typeof p === "string") : []);
6580
6927
  } catch {
6581
6928
  return /* @__PURE__ */ new Set();
6582
6929
  }
6583
6930
  }
6584
6931
  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");
6932
+ (0, import_node_fs19.mkdirSync)(appDataDir(), { recursive: true });
6933
+ (0, import_node_fs19.writeFileSync)(removedWorkspacesFile(), JSON.stringify([...paths].sort(), null, 2), "utf8");
6587
6934
  }
6588
6935
  function rememberRemoved(repoPath) {
6589
6936
  const next = readRemoved();
@@ -6606,7 +6953,7 @@ function listWorkspaces() {
6606
6953
  async function addWorkspace(repoPath) {
6607
6954
  const root = await resolveRepoRoot(repoPath);
6608
6955
  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}`);
6956
+ if (!(0, import_node_fs19.existsSync)(root)) throw new Error(`Repo not found: ${root}`);
6610
6957
  forgetRemoved(root);
6611
6958
  await ensureGhPreferOrigin(root);
6612
6959
  const current = readAll();
@@ -6636,7 +6983,7 @@ function syncWorkspacesFromThreads(repoPaths) {
6636
6983
  if (!path || path === "/" || isGlobalRepoPath(path) || byPath.has(path) || removed.has(path)) {
6637
6984
  continue;
6638
6985
  }
6639
- if (!(0, import_node_fs16.existsSync)(path)) continue;
6986
+ if (!(0, import_node_fs19.existsSync)(path)) continue;
6640
6987
  const ws = {
6641
6988
  path,
6642
6989
  name: (0, import_node_path16.basename)(path),
@@ -6649,11 +6996,11 @@ function syncWorkspacesFromThreads(repoPaths) {
6649
6996
  if (dirty) writeAll(next);
6650
6997
  return next.sort((a, b) => a.name.localeCompare(b.name));
6651
6998
  }
6652
- var import_node_fs16, import_node_path16;
6999
+ var import_node_fs19, import_node_path16;
6653
7000
  var init_workspaces = __esm({
6654
7001
  "src/store/workspaces.ts"() {
6655
7002
  "use strict";
6656
- import_node_fs16 = require("fs");
7003
+ import_node_fs19 = require("fs");
6657
7004
  import_node_path16 = require("path");
6658
7005
  init_paths();
6659
7006
  init_global_workspace();
@@ -6732,17 +7079,17 @@ __export(plan_file_exports, {
6732
7079
  });
6733
7080
  function ensureAttachmentsGitignore2(worktreePath) {
6734
7081
  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");
7082
+ if ((0, import_node_fs29.existsSync)(gitignoreAbs)) return;
7083
+ (0, import_node_fs29.mkdirSync)((0, import_node_path24.dirname)(gitignoreAbs), { recursive: true });
7084
+ (0, import_node_fs29.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
6738
7085
  }
6739
7086
  function planFileAbs(worktreePath) {
6740
7087
  return (0, import_node_path24.join)(worktreePath, PLAN_FILE_REL);
6741
7088
  }
6742
7089
  function readTextIfPresent2(abs) {
6743
- if (!(0, import_node_fs26.existsSync)(abs)) return null;
7090
+ if (!(0, import_node_fs29.existsSync)(abs)) return null;
6744
7091
  try {
6745
- const content = (0, import_node_fs26.readFileSync)(abs, "utf8");
7092
+ const content = (0, import_node_fs29.readFileSync)(abs, "utf8");
6746
7093
  return content.trim() ? content : null;
6747
7094
  } catch {
6748
7095
  return null;
@@ -6754,16 +7101,16 @@ function readPlanFile(worktreePath) {
6754
7101
  function writePlanFile(worktreePath, content) {
6755
7102
  ensureAttachmentsGitignore2(worktreePath);
6756
7103
  const abs = planFileAbs(worktreePath);
6757
- (0, import_node_fs26.mkdirSync)((0, import_node_path24.dirname)(abs), { recursive: true });
7104
+ (0, import_node_fs29.mkdirSync)((0, import_node_path24.dirname)(abs), { recursive: true });
6758
7105
  const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
6759
- (0, import_node_fs26.writeFileSync)(abs, body, "utf8");
7106
+ (0, import_node_fs29.writeFileSync)(abs, body, "utf8");
6760
7107
  return PLAN_FILE_REL;
6761
7108
  }
6762
- var import_node_fs26, import_node_path24;
7109
+ var import_node_fs29, import_node_path24;
6763
7110
  var init_plan_file = __esm({
6764
7111
  "src/plan/plan-file.ts"() {
6765
7112
  "use strict";
6766
- import_node_fs26 = require("fs");
7113
+ import_node_fs29 = require("fs");
6767
7114
  import_node_path24 = require("path");
6768
7115
  init_workspace_scratch();
6769
7116
  init_plan_present();
@@ -6780,9 +7127,9 @@ function recoverFinishedCursorRun(opts) {
6780
7127
  const agentId = opts.agentId.trim();
6781
7128
  if (!agentId) return null;
6782
7129
  const runsPath = (0, import_node_path25.join)(appDataDir(), "cursor-sdk-store", "runs.ndjson");
6783
- if (!(0, import_node_fs27.existsSync)(runsPath)) return null;
7130
+ if (!(0, import_node_fs30.existsSync)(runsPath)) return null;
6784
7131
  try {
6785
- const lines = (0, import_node_fs27.readFileSync)(runsPath, "utf8").split("\n");
7132
+ const lines = (0, import_node_fs30.readFileSync)(runsPath, "utf8").split("\n");
6786
7133
  let best = null;
6787
7134
  for (const line of lines) {
6788
7135
  const trimmed = line.trim();
@@ -6808,11 +7155,11 @@ function recoverFinishedCursorRun(opts) {
6808
7155
  return null;
6809
7156
  }
6810
7157
  }
6811
- var import_node_fs27, import_node_path25;
7158
+ var import_node_fs30, import_node_path25;
6812
7159
  var init_cursor_recover = __esm({
6813
7160
  "src/agents/cursor-recover.ts"() {
6814
7161
  "use strict";
6815
- import_node_fs27 = require("fs");
7162
+ import_node_fs30 = require("fs");
6816
7163
  import_node_path25 = require("path");
6817
7164
  init_paths();
6818
7165
  }
@@ -6826,7 +7173,7 @@ var import_node_path26 = require("path");
6826
7173
 
6827
7174
  // src/orchestrator/orchestrator.ts
6828
7175
  var import_node_events = require("events");
6829
- var import_node_fs28 = require("fs");
7176
+ var import_node_fs31 = require("fs");
6830
7177
  init_error_detail();
6831
7178
 
6832
7179
  // src/agents/spawn.ts
@@ -7216,7 +7563,7 @@ function shouldAutoArchiveOnPrMerge(opts) {
7216
7563
  }
7217
7564
 
7218
7565
  // src/hook/conductor.ts
7219
- var import_node_fs13 = require("fs");
7566
+ var import_node_fs16 = require("fs");
7220
7567
  var import_node_net = require("net");
7221
7568
  var import_node_path13 = require("path");
7222
7569
  var import_execa3 = require("execa");
@@ -7229,8 +7576,8 @@ function matchSimpleGlob(pattern, name) {
7229
7576
  }
7230
7577
  function readWorktreeInclude(repoPath) {
7231
7578
  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("#"));
7579
+ if (!(0, import_node_fs16.existsSync)(path)) return [];
7580
+ return (0, import_node_fs16.readFileSync)(path, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
7234
7581
  }
7235
7582
  function resolveFilesToCopy(repoPath) {
7236
7583
  const fromInclude = readWorktreeInclude(repoPath);
@@ -7240,7 +7587,7 @@ function resolveFilesToCopy(repoPath) {
7240
7587
  if (settings?.fileIncludeGlobs?.length) {
7241
7588
  const matched = [];
7242
7589
  try {
7243
- for (const entry of (0, import_node_fs13.readdirSync)(repoPath, { withFileTypes: true })) {
7590
+ for (const entry of (0, import_node_fs16.readdirSync)(repoPath, { withFileTypes: true })) {
7244
7591
  if (!entry.isFile()) continue;
7245
7592
  for (const glob of settings.fileIncludeGlobs) {
7246
7593
  if (matchSimpleGlob(glob, entry.name) || matchSimpleGlob((0, import_node_path13.basename)(glob), entry.name)) {
@@ -7255,7 +7602,7 @@ function resolveFilesToCopy(repoPath) {
7255
7602
  }
7256
7603
  const defaults = [];
7257
7604
  try {
7258
- for (const entry of (0, import_node_fs13.readdirSync)(repoPath, { withFileTypes: true })) {
7605
+ for (const entry of (0, import_node_fs16.readdirSync)(repoPath, { withFileTypes: true })) {
7259
7606
  if (entry.isFile() && entry.name.startsWith(".env")) {
7260
7607
  defaults.push(entry.name);
7261
7608
  }
@@ -7270,10 +7617,10 @@ function copyConfiguredFiles(repoPath, worktreePath) {
7270
7617
  const copied = [];
7271
7618
  for (const rel of patterns) {
7272
7619
  const src = (0, import_node_path13.join)(repoPath, rel);
7273
- if (!(0, import_node_fs13.existsSync)(src)) continue;
7620
+ if (!(0, import_node_fs16.existsSync)(src)) continue;
7274
7621
  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);
7622
+ (0, import_node_fs16.mkdirSync)((0, import_node_path13.dirname)(dest), { recursive: true });
7623
+ (0, import_node_fs16.copyFileSync)(src, dest);
7277
7624
  copied.push(rel);
7278
7625
  }
7279
7626
  return copied;
@@ -7526,15 +7873,15 @@ async function startDevServer(repoPath, worktreePath, onLine, opts) {
7526
7873
  }
7527
7874
 
7528
7875
  // src/hook/cursor-worktrees.ts
7529
- var import_node_fs14 = require("fs");
7876
+ var import_node_fs17 = require("fs");
7530
7877
  var import_node_path14 = require("path");
7531
7878
  var import_execa4 = require("execa");
7532
7879
  var import_node_readline3 = require("readline");
7533
7880
  function loadCursorWorktreesJson(rootPath) {
7534
7881
  const path = (0, import_node_path14.join)(rootPath, ".cursor", "worktrees.json");
7535
- if (!(0, import_node_fs14.existsSync)(path)) return null;
7882
+ if (!(0, import_node_fs17.existsSync)(path)) return null;
7536
7883
  try {
7537
- return JSON.parse((0, import_node_fs14.readFileSync)(path, "utf8"));
7884
+ return JSON.parse((0, import_node_fs17.readFileSync)(path, "utf8"));
7538
7885
  } catch {
7539
7886
  return null;
7540
7887
  }
@@ -7592,7 +7939,7 @@ async function runCursorWorktreeSetup(repoPath, worktreePath, onLine) {
7592
7939
  }
7593
7940
 
7594
7941
  // src/git/orphan-cleanup.ts
7595
- var import_node_fs15 = require("fs");
7942
+ var import_node_fs18 = require("fs");
7596
7943
  var import_node_path15 = require("path");
7597
7944
  init_worktree();
7598
7945
  init_thread_store();
@@ -7608,9 +7955,9 @@ async function findOrphanWorktrees(repoPaths) {
7608
7955
  repoPaths?.length ? repoPaths : threads.map((t) => t.repoPath).filter(Boolean)
7609
7956
  );
7610
7957
  const homeRoot = sideboardWorkspacesDir();
7611
- if ((0, import_node_fs15.existsSync)(homeRoot)) {
7958
+ if ((0, import_node_fs18.existsSync)(homeRoot)) {
7612
7959
  try {
7613
- for (const entry of (0, import_node_fs15.readdirSync)(homeRoot, { withFileTypes: true })) {
7960
+ for (const entry of (0, import_node_fs18.readdirSync)(homeRoot, { withFileTypes: true })) {
7614
7961
  if (!entry.isDirectory()) continue;
7615
7962
  void entry;
7616
7963
  }
@@ -7620,7 +7967,7 @@ async function findOrphanWorktrees(repoPaths) {
7620
7967
  const orphans = [];
7621
7968
  const seen = /* @__PURE__ */ new Set();
7622
7969
  for (const repoPath of repos) {
7623
- if (!repoPath || !(0, import_node_fs15.existsSync)(repoPath)) continue;
7970
+ if (!repoPath || !(0, import_node_fs18.existsSync)(repoPath)) continue;
7624
7971
  try {
7625
7972
  const wts = await listWorktrees(repoPath);
7626
7973
  for (const wt of wts) {
@@ -7631,7 +7978,7 @@ async function findOrphanWorktrees(repoPaths) {
7631
7978
  seen.add(path);
7632
7979
  let mtimeMs = 0;
7633
7980
  try {
7634
- mtimeMs = (0, import_node_fs15.statSync)(path).mtimeMs;
7981
+ mtimeMs = (0, import_node_fs18.statSync)(path).mtimeMs;
7635
7982
  } catch {
7636
7983
  mtimeMs = 0;
7637
7984
  }
@@ -7641,16 +7988,16 @@ async function findOrphanWorktrees(repoPaths) {
7641
7988
  }
7642
7989
  try {
7643
7990
  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 })) {
7991
+ if ((0, import_node_fs18.existsSync)(root)) {
7992
+ for (const entry of (0, import_node_fs18.readdirSync)(root, { withFileTypes: true })) {
7646
7993
  if (!entry.isDirectory()) continue;
7647
7994
  const path = (0, import_node_path15.join)(root, entry.name).replace(/\/$/, "");
7648
7995
  if (known.has(path) || seen.has(path)) continue;
7649
- if (!(0, import_node_fs15.existsSync)((0, import_node_path15.join)(path, ".git"))) continue;
7996
+ if (!(0, import_node_fs18.existsSync)((0, import_node_path15.join)(path, ".git"))) continue;
7650
7997
  seen.add(path);
7651
7998
  let mtimeMs = 0;
7652
7999
  try {
7653
- mtimeMs = (0, import_node_fs15.statSync)(path).mtimeMs;
8000
+ mtimeMs = (0, import_node_fs18.statSync)(path).mtimeMs;
7654
8001
  } catch {
7655
8002
  mtimeMs = Date.now();
7656
8003
  }
@@ -7787,7 +8134,7 @@ async function applyThreadIntoMain(thread, opts) {
7787
8134
  }
7788
8135
 
7789
8136
  // src/git/clone-repo.ts
7790
- var import_node_fs17 = require("fs");
8137
+ var import_node_fs20 = require("fs");
7791
8138
  var import_node_path17 = require("path");
7792
8139
  var import_execa6 = require("execa");
7793
8140
  init_paths();
@@ -7803,7 +8150,7 @@ async function cloneRepoIntoSideboard(opts) {
7803
8150
  }
7804
8151
  name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
7805
8152
  const dest = (0, import_node_path17.join)(sideboardReposDir(), name);
7806
- if ((0, import_node_fs17.existsSync)(dest)) {
8153
+ if ((0, import_node_fs20.existsSync)(dest)) {
7807
8154
  const repoPath2 = await resolveRepoRoot(dest);
7808
8155
  const workspace2 = await ensureWorkspace(repoPath2);
7809
8156
  return { repoPath: repoPath2, workspace: workspace2 };
@@ -7823,13 +8170,14 @@ async function cloneRepoIntoSideboard(opts) {
7823
8170
  init_thread_store();
7824
8171
 
7825
8172
  // src/threads/create.ts
7826
- var import_node_fs18 = require("fs");
8173
+ var import_node_fs21 = require("fs");
7827
8174
 
7828
8175
  // src/detect/detect.ts
7829
8176
  init_agents();
7830
8177
  var REQUIRE_AGENT_TIMEOUT_MS = 8e3;
7831
8178
  async function requireAgent(agent, opts) {
7832
8179
  ensureAgentPath();
8180
+ enrichPathWithNpmGlobalBin();
7833
8181
  const status = await Promise.race([
7834
8182
  getAdapter(agent).detect(),
7835
8183
  new Promise((_, reject) => {
@@ -7867,7 +8215,7 @@ async function createThread(input, _onSetupLine) {
7867
8215
  });
7868
8216
  await requireAgent(resolved.agent);
7869
8217
  const repoPath = await resolveRepoRoot(input.repoPath);
7870
- if (!(0, import_node_fs18.existsSync)(repoPath)) {
8218
+ if (!(0, import_node_fs21.existsSync)(repoPath)) {
7871
8219
  throw new Error(`Repo not found: ${repoPath}`);
7872
8220
  }
7873
8221
  let sourceRef = input.sourceRef;
@@ -8330,7 +8678,7 @@ function forkChatTab(input) {
8330
8678
 
8331
8679
  // src/review/request-review.ts
8332
8680
  var import_node_crypto3 = require("crypto");
8333
- var import_node_fs19 = require("fs");
8681
+ var import_node_fs22 = require("fs");
8334
8682
  var import_node_path18 = require("path");
8335
8683
  init_global_workspace();
8336
8684
  init_thread_store();
@@ -8475,9 +8823,9 @@ function shouldRefreshReviewRequestTemplate(content) {
8475
8823
  return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
8476
8824
  }
8477
8825
  function readTextIfPresent(abs) {
8478
- if (!(0, import_node_fs19.existsSync)(abs)) return null;
8826
+ if (!(0, import_node_fs22.existsSync)(abs)) return null;
8479
8827
  try {
8480
- const content = (0, import_node_fs19.readFileSync)(abs, "utf8");
8828
+ const content = (0, import_node_fs22.readFileSync)(abs, "utf8");
8481
8829
  return content.trim() ? content : null;
8482
8830
  } catch {
8483
8831
  return null;
@@ -8485,9 +8833,9 @@ function readTextIfPresent(abs) {
8485
8833
  }
8486
8834
  function ensureAttachmentsGitignore(worktreePath) {
8487
8835
  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");
8836
+ if ((0, import_node_fs22.existsSync)(gitignoreAbs)) return;
8837
+ (0, import_node_fs22.mkdirSync)((0, import_node_path18.dirname)(gitignoreAbs), { recursive: true });
8838
+ (0, import_node_fs22.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
8491
8839
  }
8492
8840
  function resolveReviewGuidelines(worktreePath) {
8493
8841
  const repoAbs = (0, import_node_path18.join)(worktreePath, REPO_REVIEW_PATH);
@@ -8521,8 +8869,8 @@ function resolveReviewGuidelines(worktreePath) {
8521
8869
  };
8522
8870
  }
8523
8871
  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");
8872
+ (0, import_node_fs22.mkdirSync)((0, import_node_path18.dirname)(localAbs), { recursive: true });
8873
+ (0, import_node_fs22.writeFileSync)(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
8526
8874
  return {
8527
8875
  path: REVIEW_REQUEST_PATH,
8528
8876
  name: REVIEW_REQUEST_NAME,
@@ -8721,8 +9069,8 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
8721
9069
  }
8722
9070
 
8723
9071
  // src/threads/adopt.ts
8724
- var import_node_child_process = require("child_process");
8725
- var import_node_fs20 = require("fs");
9072
+ var import_node_child_process2 = require("child_process");
9073
+ var import_node_fs23 = require("fs");
8726
9074
  var import_node_os8 = require("os");
8727
9075
  var import_node_path19 = require("path");
8728
9076
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
@@ -8747,21 +9095,21 @@ function mapAgentType(raw) {
8747
9095
  return null;
8748
9096
  }
8749
9097
  function resolveConductorCursorAgentId(workspacePath) {
8750
- if (!workspacePath || !(0, import_node_fs20.existsSync)(CURSOR_SDK_STORE)) return null;
9098
+ if (!workspacePath || !(0, import_node_fs23.existsSync)(CURSOR_SDK_STORE)) return null;
8751
9099
  const normalized = workspacePath.replace(/\/$/, "");
8752
9100
  let best = null;
8753
9101
  let hashes;
8754
9102
  try {
8755
- hashes = (0, import_node_fs20.readdirSync)(CURSOR_SDK_STORE);
9103
+ hashes = (0, import_node_fs23.readdirSync)(CURSOR_SDK_STORE);
8756
9104
  } catch {
8757
9105
  return null;
8758
9106
  }
8759
9107
  for (const hash of hashes) {
8760
9108
  const agentsFile = (0, import_node_path19.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
8761
- if (!(0, import_node_fs20.existsSync)(agentsFile)) continue;
9109
+ if (!(0, import_node_fs23.existsSync)(agentsFile)) continue;
8762
9110
  let text;
8763
9111
  try {
8764
- text = (0, import_node_fs20.readFileSync)(agentsFile, "utf8");
9112
+ text = (0, import_node_fs23.readFileSync)(agentsFile, "utf8");
8765
9113
  } catch {
8766
9114
  continue;
8767
9115
  }
@@ -8785,7 +9133,7 @@ function resolveConductorCursorAgentId(workspacePath) {
8785
9133
  return best?.agentId ?? null;
8786
9134
  }
8787
9135
  async function adoptThread(input) {
8788
- if (!(0, import_node_fs20.existsSync)(input.worktreePath)) {
9136
+ if (!(0, import_node_fs23.existsSync)(input.worktreePath)) {
8789
9137
  throw new Error(`Worktree not found: ${input.worktreePath}`);
8790
9138
  }
8791
9139
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -8809,18 +9157,18 @@ async function adoptThread(input) {
8809
9157
  return thread;
8810
9158
  }
8811
9159
  function listConductorWorkspaces() {
8812
- if (!(0, import_node_fs20.existsSync)(CONDUCTOR_DB)) {
9160
+ if (!(0, import_node_fs23.existsSync)(CONDUCTOR_DB)) {
8813
9161
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
8814
9162
  }
8815
- const tmp = (0, import_node_fs20.mkdtempSync)((0, import_node_path19.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
9163
+ const tmp = (0, import_node_fs23.mkdtempSync)((0, import_node_path19.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
8816
9164
  const snapshot = (0, import_node_path19.join)(tmp, "conductor.db");
8817
9165
  try {
8818
- (0, import_node_fs20.copyFileSync)(CONDUCTOR_DB, snapshot);
9166
+ (0, import_node_fs23.copyFileSync)(CONDUCTOR_DB, snapshot);
8819
9167
  for (const suffix of ["-wal", "-shm"]) {
8820
9168
  const src = `${CONDUCTOR_DB}${suffix}`;
8821
- if ((0, import_node_fs20.existsSync)(src)) {
9169
+ if ((0, import_node_fs23.existsSync)(src)) {
8822
9170
  try {
8823
- (0, import_node_fs20.copyFileSync)(src, `${snapshot}${suffix}`);
9171
+ (0, import_node_fs23.copyFileSync)(src, `${snapshot}${suffix}`);
8824
9172
  } catch {
8825
9173
  }
8826
9174
  }
@@ -8896,22 +9244,22 @@ function listConductorWorkspaces() {
8896
9244
  db.close();
8897
9245
  }
8898
9246
  } finally {
8899
- (0, import_node_fs20.rmSync)(tmp, { recursive: true, force: true });
9247
+ (0, import_node_fs23.rmSync)(tmp, { recursive: true, force: true });
8900
9248
  }
8901
9249
  }
8902
9250
  function importConductorWorkspace(workspaceId) {
8903
- if (!(0, import_node_fs20.existsSync)(CONDUCTOR_DB)) {
9251
+ if (!(0, import_node_fs23.existsSync)(CONDUCTOR_DB)) {
8904
9252
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
8905
9253
  }
8906
- const tmp = (0, import_node_fs20.mkdtempSync)((0, import_node_path19.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
9254
+ const tmp = (0, import_node_fs23.mkdtempSync)((0, import_node_path19.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
8907
9255
  const snapshot = (0, import_node_path19.join)(tmp, "conductor.db");
8908
9256
  try {
8909
- (0, import_node_fs20.copyFileSync)(CONDUCTOR_DB, snapshot);
9257
+ (0, import_node_fs23.copyFileSync)(CONDUCTOR_DB, snapshot);
8910
9258
  for (const suffix of ["-wal", "-shm"]) {
8911
9259
  const src = `${CONDUCTOR_DB}${suffix}`;
8912
- if ((0, import_node_fs20.existsSync)(src)) {
9260
+ if ((0, import_node_fs23.existsSync)(src)) {
8913
9261
  try {
8914
- (0, import_node_fs20.copyFileSync)(src, `${snapshot}${suffix}`);
9262
+ (0, import_node_fs23.copyFileSync)(src, `${snapshot}${suffix}`);
8915
9263
  } catch {
8916
9264
  }
8917
9265
  }
@@ -8929,7 +9277,7 @@ function importConductorWorkspace(workspaceId) {
8929
9277
  ).get(workspaceId);
8930
9278
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
8931
9279
  const worktreePath = String(row.workspacePath);
8932
- if (!(0, import_node_fs20.existsSync)(worktreePath)) {
9280
+ if (!(0, import_node_fs23.existsSync)(worktreePath)) {
8933
9281
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
8934
9282
  }
8935
9283
  let sessionId = null;
@@ -8969,7 +9317,7 @@ function importConductorWorkspace(workspaceId) {
8969
9317
  }
8970
9318
  let repoPath = worktreePath;
8971
9319
  try {
8972
- repoPath = (0, import_node_child_process.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
9320
+ repoPath = (0, import_node_child_process2.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
8973
9321
  cwd: worktreePath,
8974
9322
  encoding: "utf8"
8975
9323
  }).trim();
@@ -8992,7 +9340,7 @@ function importConductorWorkspace(workspaceId) {
8992
9340
  db.close();
8993
9341
  }
8994
9342
  } finally {
8995
- (0, import_node_fs20.rmSync)(tmp, { recursive: true, force: true });
9343
+ (0, import_node_fs23.rmSync)(tmp, { recursive: true, force: true });
8996
9344
  }
8997
9345
  }
8998
9346
  async function importConductorWorkspaceAsync(workspaceId) {
@@ -9000,7 +9348,7 @@ async function importConductorWorkspaceAsync(workspaceId) {
9000
9348
  }
9001
9349
 
9002
9350
  // src/threads/stack-layers.ts
9003
- var import_node_fs21 = require("fs");
9351
+ var import_node_fs24 = require("fs");
9004
9352
  init_run();
9005
9353
  init_stack();
9006
9354
  init_worktree();
@@ -9068,7 +9416,7 @@ async function openStackLayer(input, _onSetupLine) {
9068
9416
  let createdWorktree = false;
9069
9417
  const trees = await listWorktrees(repoPath);
9070
9418
  const checkedOut = trees.find((w) => w.branch === branchName);
9071
- if (checkedOut?.path && (0, import_node_fs21.existsSync)(checkedOut.path)) {
9419
+ if (checkedOut?.path && (0, import_node_fs24.existsSync)(checkedOut.path)) {
9072
9420
  if (input.reuseExistingWorktree !== false) {
9073
9421
  worktreePath = checkedOut.path;
9074
9422
  } else {
@@ -9210,7 +9558,7 @@ async function initStackFromThread(input, onSetupLine) {
9210
9558
  async function createPrStack(input, onSetupLine) {
9211
9559
  await requireAgent(input.agent);
9212
9560
  const repoPath = await resolveRepoRoot(input.repoPath);
9213
- if (!(0, import_node_fs21.existsSync)(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
9561
+ if (!(0, import_node_fs24.existsSync)(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
9214
9562
  if (!input.branches.length) throw new Error("At least one branch name required");
9215
9563
  const status = await detectGhStack(repoPath);
9216
9564
  if (!status.available) throw new Error(status.reason);
@@ -9277,7 +9625,7 @@ async function createPrStack(input, onSetupLine) {
9277
9625
  }
9278
9626
  }
9279
9627
  const claimed = new Set(threads.map((t) => t.worktreePath));
9280
- if (!claimed.has(bootstrap.worktreePath) && (0, import_node_fs21.existsSync)(bootstrap.worktreePath)) {
9628
+ if (!claimed.has(bootstrap.worktreePath) && (0, import_node_fs24.existsSync)(bootstrap.worktreePath)) {
9281
9629
  try {
9282
9630
  await removeWorktree(repoPath, bootstrap.worktreePath, {
9283
9631
  deleteBranch: bootstrap.branchName
@@ -9292,12 +9640,12 @@ async function createPrStack(input, onSetupLine) {
9292
9640
  init_worktree();
9293
9641
 
9294
9642
  // src/diff/diff.ts
9295
- var import_node_fs22 = require("fs");
9643
+ var import_node_fs25 = require("fs");
9296
9644
  var import_node_path20 = require("path");
9297
9645
  init_run();
9298
9646
  init_worktree();
9299
9647
  async function inspectGitWorktree(worktreePath) {
9300
- if (!worktreePath || !(0, import_node_fs22.existsSync)(worktreePath)) return "missing_worktree";
9648
+ if (!worktreePath || !(0, import_node_fs25.existsSync)(worktreePath)) return "missing_worktree";
9301
9649
  const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
9302
9650
  reject: false
9303
9651
  });
@@ -9305,7 +9653,7 @@ async function inspectGitWorktree(worktreePath) {
9305
9653
  return "ok";
9306
9654
  }
9307
9655
  async function initializeGitRepository(worktreePath) {
9308
- if (!worktreePath || !(0, import_node_fs22.existsSync)(worktreePath)) {
9656
+ if (!worktreePath || !(0, import_node_fs25.existsSync)(worktreePath)) {
9309
9657
  throw new Error("Worktree not found");
9310
9658
  }
9311
9659
  const status = await inspectGitWorktree(worktreePath);
@@ -9743,7 +10091,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
9743
10091
  assertSafeRelativePath(relativePath);
9744
10092
  const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
9745
10093
  const abs = (0, import_node_path20.join)(worktreePath, relativePath);
9746
- const st = (0, import_node_fs22.statSync)(abs);
10094
+ const st = (0, import_node_fs25.statSync)(abs);
9747
10095
  if (!st.isFile()) {
9748
10096
  throw new Error(`Not a file: ${relativePath}`);
9749
10097
  }
@@ -9752,7 +10100,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
9752
10100
  `File too large to upload (${st.size} bytes; max ${maxBytes})`
9753
10101
  );
9754
10102
  }
9755
- const buf = (0, import_node_fs22.readFileSync)(abs);
10103
+ const buf = (0, import_node_fs25.readFileSync)(abs);
9756
10104
  return {
9757
10105
  path: relativePath,
9758
10106
  contentBase64: buf.toString("base64"),
@@ -9763,11 +10111,11 @@ function readWorktreeFile(worktreePath, relativePath, opts) {
9763
10111
  assertSafeRelativePath(relativePath);
9764
10112
  const maxBytes = opts?.maxBytes ?? 2e5;
9765
10113
  const abs = (0, import_node_path20.join)(worktreePath, relativePath);
9766
- const st = (0, import_node_fs22.statSync)(abs);
10114
+ const st = (0, import_node_fs25.statSync)(abs);
9767
10115
  if (!st.isFile()) {
9768
10116
  throw new Error(`Not a file: ${relativePath}`);
9769
10117
  }
9770
- const buf = (0, import_node_fs22.readFileSync)(abs);
10118
+ const buf = (0, import_node_fs25.readFileSync)(abs);
9771
10119
  if (isImageRelativePath(relativePath)) {
9772
10120
  const maxImageBytes = Math.max(maxBytes, 15e6);
9773
10121
  const truncated2 = buf.length > maxImageBytes;
@@ -9811,8 +10159,8 @@ function assertSafeRelativePath(relativePath) {
9811
10159
  function writeWorktreeFile(worktreePath, relativePath, content) {
9812
10160
  assertSafeRelativePath(relativePath);
9813
10161
  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");
10162
+ (0, import_node_fs25.mkdirSync)((0, import_node_path20.dirname)(abs), { recursive: true });
10163
+ (0, import_node_fs25.writeFileSync)(abs, content, "utf8");
9816
10164
  return { path: relativePath };
9817
10165
  }
9818
10166
  async function getDiffSummary(worktreePath, repoPath, opts) {
@@ -9991,7 +10339,7 @@ async function confirmLand(thread, opts) {
9991
10339
  }
9992
10340
 
9993
10341
  // src/skills/discover.ts
9994
- var import_node_fs23 = require("fs");
10342
+ var import_node_fs26 = require("fs");
9995
10343
  var import_node_os9 = require("os");
9996
10344
  var import_node_path21 = require("path");
9997
10345
  function toCommand(name) {
@@ -10025,7 +10373,7 @@ function parseFrontmatter(content) {
10025
10373
  }
10026
10374
  function readSkill(skillMd, source) {
10027
10375
  try {
10028
- const content = (0, import_node_fs23.readFileSync)(skillMd, "utf8");
10376
+ const content = (0, import_node_fs26.readFileSync)(skillMd, "utf8");
10029
10377
  const { name: fmName, description } = parseFrontmatter(content);
10030
10378
  const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
10031
10379
  const name = fmName || dirName;
@@ -10044,19 +10392,19 @@ function readSkill(skillMd, source) {
10044
10392
  }
10045
10393
  }
10046
10394
  function scanSkillsDir(dir, source, out) {
10047
- if (!(0, import_node_fs23.existsSync)(dir)) return;
10395
+ if (!(0, import_node_fs26.existsSync)(dir)) return;
10048
10396
  let entries;
10049
10397
  try {
10050
- entries = (0, import_node_fs23.readdirSync)(dir);
10398
+ entries = (0, import_node_fs26.readdirSync)(dir);
10051
10399
  } catch {
10052
10400
  return;
10053
10401
  }
10054
10402
  for (const entry of entries) {
10055
10403
  if (entry.startsWith(".")) continue;
10056
10404
  const skillMd = (0, import_node_path21.join)(dir, entry, "SKILL.md");
10057
- if (!(0, import_node_fs23.existsSync)(skillMd)) continue;
10405
+ if (!(0, import_node_fs26.existsSync)(skillMd)) continue;
10058
10406
  try {
10059
- if (!(0, import_node_fs23.statSync)(skillMd).isFile()) continue;
10407
+ if (!(0, import_node_fs26.statSync)(skillMd).isFile()) continue;
10060
10408
  } catch {
10061
10409
  continue;
10062
10410
  }
@@ -10065,12 +10413,12 @@ function scanSkillsDir(dir, source, out) {
10065
10413
  }
10066
10414
  }
10067
10415
  function scanClaudePluginSkills(pluginsRoot, out) {
10068
- if (!(0, import_node_fs23.existsSync)(pluginsRoot)) return;
10416
+ if (!(0, import_node_fs26.existsSync)(pluginsRoot)) return;
10069
10417
  const walk = (dir, depth, lookingForSkillsDir) => {
10070
10418
  if (depth > 7) return;
10071
10419
  let entries;
10072
10420
  try {
10073
- entries = (0, import_node_fs23.readdirSync)(dir);
10421
+ entries = (0, import_node_fs26.readdirSync)(dir);
10074
10422
  } catch {
10075
10423
  return;
10076
10424
  }
@@ -10082,7 +10430,7 @@ function scanClaudePluginSkills(pluginsRoot, out) {
10082
10430
  if (entry === "node_modules" || entry === ".git") continue;
10083
10431
  const full = (0, import_node_path21.join)(dir, entry);
10084
10432
  try {
10085
- if (!(0, import_node_fs23.statSync)(full).isDirectory()) continue;
10433
+ if (!(0, import_node_fs26.statSync)(full).isDirectory()) continue;
10086
10434
  } catch {
10087
10435
  continue;
10088
10436
  }
@@ -10122,7 +10470,7 @@ function discoverSkills(worktreePath) {
10122
10470
  return [...byCommand.values()].sort((a, b) => a.command.localeCompare(b.command));
10123
10471
  }
10124
10472
  function readSkillBody(skillPath, maxChars = 12e3) {
10125
- const raw = (0, import_node_fs23.readFileSync)(skillPath, "utf8");
10473
+ const raw = (0, import_node_fs26.readFileSync)(skillPath, "utf8");
10126
10474
  if (raw.startsWith("---")) {
10127
10475
  const end = raw.indexOf("\n---", 3);
10128
10476
  if (end >= 0) {
@@ -10215,7 +10563,7 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
10215
10563
  }
10216
10564
 
10217
10565
  // src/composer/stage-files.ts
10218
- var import_node_fs24 = require("fs");
10566
+ var import_node_fs27 = require("fs");
10219
10567
  var import_node_path22 = require("path");
10220
10568
  var import_node_crypto5 = require("crypto");
10221
10569
  init_workspace_scratch();
@@ -10253,21 +10601,21 @@ function imageMimeType(filePath) {
10253
10601
  }
10254
10602
  function ensureAttachmentsDir(worktreePath) {
10255
10603
  const dir = (0, import_node_path22.join)(worktreePath, ATTACHMENTS_DIR);
10256
- (0, import_node_fs24.mkdirSync)(dir, { recursive: true });
10604
+ (0, import_node_fs27.mkdirSync)(dir, { recursive: true });
10257
10605
  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");
10606
+ if (!(0, import_node_fs27.existsSync)(gi)) {
10607
+ (0, import_node_fs27.writeFileSync)(gi, attachmentsGitignoreBody(), "utf8");
10260
10608
  }
10261
10609
  return dir;
10262
10610
  }
10263
10611
  function uniqueAttachmentName(dir, originalName) {
10264
10612
  const safe = originalName.replace(/[/\\]/g, "_") || "file";
10265
- if (!(0, import_node_fs24.existsSync)((0, import_node_path22.join)(dir, safe))) return safe;
10613
+ if (!(0, import_node_fs27.existsSync)((0, import_node_path22.join)(dir, safe))) return safe;
10266
10614
  const ext = (0, import_node_path22.extname)(safe);
10267
10615
  const stem = ext ? safe.slice(0, -ext.length) : safe;
10268
10616
  for (let i = 1; i < 1e4; i++) {
10269
10617
  const candidate = `${stem}-${i}${ext}`;
10270
- if (!(0, import_node_fs24.existsSync)((0, import_node_path22.join)(dir, candidate))) return candidate;
10618
+ if (!(0, import_node_fs27.existsSync)((0, import_node_path22.join)(dir, candidate))) return candidate;
10271
10619
  }
10272
10620
  return `${stem}-${(0, import_node_crypto5.randomUUID)()}${ext}`;
10273
10621
  }
@@ -10325,13 +10673,13 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
10325
10673
  for (const abs of absolutePaths) {
10326
10674
  const originalName = (0, import_node_path22.basename)(abs);
10327
10675
  try {
10328
- const st = (0, import_node_fs24.statSync)(abs);
10676
+ const st = (0, import_node_fs27.statSync)(abs);
10329
10677
  if (!st.isFile()) continue;
10330
10678
  const name = uniqueAttachmentName(dir, originalName);
10331
10679
  const destAbs = (0, import_node_path22.join)(dir, name);
10332
- (0, import_node_fs24.copyFileSync)(abs, destAbs);
10680
+ (0, import_node_fs27.copyFileSync)(abs, destAbs);
10333
10681
  const rel = `${ATTACHMENTS_DIR}/${name}`;
10334
- const buf = (0, import_node_fs24.readFileSync)(destAbs);
10682
+ const buf = (0, import_node_fs27.readFileSync)(destAbs);
10335
10683
  out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
10336
10684
  } catch (err) {
10337
10685
  out.push({
@@ -10354,7 +10702,7 @@ function stageBuffersAsAttachments(worktreePath, buffers) {
10354
10702
  const buf = Buffer.from(item.dataBase64, "base64");
10355
10703
  const name = uniqueAttachmentName(dir, originalName);
10356
10704
  const destAbs = (0, import_node_path22.join)(dir, name);
10357
- (0, import_node_fs24.writeFileSync)(destAbs, buf);
10705
+ (0, import_node_fs27.writeFileSync)(destAbs, buf);
10358
10706
  const rel = `${ATTACHMENTS_DIR}/${name}`;
10359
10707
  out.push(attachmentFromBuffer(name, buf, { path: rel }));
10360
10708
  } catch (err) {
@@ -10383,9 +10731,9 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
10383
10731
  const name = (0, import_node_path22.basename)(rel);
10384
10732
  try {
10385
10733
  const abs = (0, import_node_path22.join)(worktreePath, rel);
10386
- const st = (0, import_node_fs24.statSync)(abs);
10734
+ const st = (0, import_node_fs27.statSync)(abs);
10387
10735
  if (!st.isFile()) continue;
10388
- const buf = (0, import_node_fs24.readFileSync)(abs);
10736
+ const buf = (0, import_node_fs27.readFileSync)(abs);
10389
10737
  out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
10390
10738
  } catch (err) {
10391
10739
  out.push({
@@ -10400,7 +10748,7 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
10400
10748
  }
10401
10749
 
10402
10750
  // src/agents/instructions.ts
10403
- var import_node_fs25 = require("fs");
10751
+ var import_node_fs28 = require("fs");
10404
10752
  var import_node_path23 = require("path");
10405
10753
  init_worktree_labels();
10406
10754
  function normPath2(p) {
@@ -10500,6 +10848,9 @@ function formatWorktreeDirective(thread, opts) {
10500
10848
  );
10501
10849
  return lines.join("\n");
10502
10850
  }
10851
+ function formatWorktreeReminder() {
10852
+ return "Sideboard worktree: stay in this cwd for all file and git work. Push and open PRs against origin, never upstream. Do not edit the main repo checkout.";
10853
+ }
10503
10854
  function formatArtifactDirective() {
10504
10855
  return [
10505
10856
  "Sideboard side column (desktop UI):",
@@ -10520,60 +10871,6 @@ function formatArtifactDirective() {
10520
10871
  "Never say artifacts, CMS UI, or the Files column are unavailable. Prefer present_schema for list/edit/publish; present_files for storage UI; html fences for standalone pages."
10521
10872
  ].join("\n");
10522
10873
  }
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
10874
 
10578
10875
  // src/orchestrator/orchestrator.ts
10579
10876
  init_types();
@@ -10698,7 +10995,7 @@ var Orchestrator = class {
10698
10995
  }
10699
10996
  continue;
10700
10997
  }
10701
- if (!(0, import_node_fs28.existsSync)(thread.worktreePath)) {
10998
+ if (!(0, import_node_fs31.existsSync)(thread.worktreePath)) {
10702
10999
  setStatus(thread.id, "broken", "Worktree missing on disk");
10703
11000
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
10704
11001
  continue;
@@ -11115,13 +11412,14 @@ var Orchestrator = class {
11115
11412
  "Files column: MCP present_files (brightsy account storage or memory).",
11116
11413
  "Do not say artifacts/CMS UI are unavailable."
11117
11414
  ].join(" ") : null;
11415
+ const worktreeReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatWorktreeReminder() : null;
11118
11416
  const agentPrompt = [
11119
11417
  thread.planMode ? PLAN_MODE_INSTRUCTION : null,
11120
11418
  orchestrationReminder,
11419
+ worktreeReminder,
11121
11420
  artifactReminder,
11122
11421
  expandedPrompt
11123
11422
  ].filter(Boolean).join("\n\n");
11124
- const instructionFiles = loadAgentInstructions(thread.worktreePath, thread.agent);
11125
11423
  if (thread.attachments.length > 0) {
11126
11424
  updateThread(threadId, { attachments: [] });
11127
11425
  }
@@ -11149,7 +11447,6 @@ var Orchestrator = class {
11149
11447
  const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled2() ? formatRenameBranchDirective(fresh, {
11150
11448
  customPrompt: settings?.prompts?.renameBranch
11151
11449
  }) : null;
11152
- const instructions = fresh.agent === "claude" || isBrightsy ? null : formatAgentInstructions(instructionFiles);
11153
11450
  let seed = null;
11154
11451
  if (!fresh.sessionId) {
11155
11452
  const prior = fresh.messages.slice(0, -1);
@@ -11170,12 +11467,12 @@ var Orchestrator = class {
11170
11467
  });
11171
11468
  }
11172
11469
  }
11173
- const cachedPrefix = [
11470
+ const cachedPrefix = fresh.sessionId ? "" : [
11174
11471
  coordinatorDirective,
11175
11472
  worktreeDirective,
11176
11473
  artifactDirective,
11177
11474
  renameBranchDirective,
11178
- ...fresh.agent === "claude" && fresh.sessionId ? [] : [instructions, seed]
11475
+ seed
11179
11476
  ].filter(Boolean).join("\n\n---\n\n");
11180
11477
  try {
11181
11478
  const stderrTail = [];
@@ -11262,15 +11559,11 @@ var Orchestrator = class {
11262
11559
  const retryThread = this.requireThread(threadId);
11263
11560
  const prior = retryThread.messages.slice(0, -1);
11264
11561
  const retrySeed = buildSessionSeed(prior);
11265
- const retryInstructions = retryThread.agent === "claude" ? null : formatAgentInstructions(
11266
- loadAgentInstructions(retryThread.worktreePath, retryThread.agent)
11267
- );
11268
11562
  const retryPrefix = [
11269
11563
  coordinatorDirective,
11270
11564
  worktreeDirective,
11271
11565
  artifactDirective,
11272
11566
  renameBranchDirective,
11273
- retryInstructions,
11274
11567
  retrySeed
11275
11568
  ].filter(Boolean).join("\n\n---\n\n");
11276
11569
  const retryHandle = await spawnAgentTurn(
@@ -12105,7 +12398,7 @@ var Orchestrator = class {
12105
12398
  this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
12106
12399
  return restored2;
12107
12400
  }
12108
- if (!(0, import_node_fs28.existsSync)(thread.worktreePath)) {
12401
+ if (!(0, import_node_fs31.existsSync)(thread.worktreePath)) {
12109
12402
  const { createThreadWorktree: createThreadWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
12110
12403
  const { execa: execa7 } = await import("execa");
12111
12404
  const slug = thread.worktreePath.split("/").pop();
@@ -12314,6 +12607,7 @@ function mcpArchiveBlockedReason(thread) {
12314
12607
  }
12315
12608
 
12316
12609
  // src/mcp/server.ts
12610
+ init_profile();
12317
12611
  var MAX_ORCH_THREADS = 5;
12318
12612
  var CREATE_THREAD_TIMEOUT_MS = 9e4;
12319
12613
  function withTimeout(promise, ms, label) {
@@ -12427,14 +12721,13 @@ async function startMcpServer() {
12427
12721
  ),
12428
12722
  artifact_id: import_zod.z.string().optional().describe("Stable id when updating the same artifact across turns")
12429
12723
  },
12430
- async ({ title, type, content, artifact_id }) => {
12724
+ async ({ title, type, artifact_id }) => {
12431
12725
  const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
12432
12726
  const payload = {
12433
12727
  ok: true,
12434
12728
  artifact_id: id,
12435
12729
  title,
12436
12730
  type,
12437
- content,
12438
12731
  message: "Artifact accepted. Sideboard desktop opens it in the side column beside chat."
12439
12732
  };
12440
12733
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
@@ -12487,7 +12780,6 @@ async function startMcpServer() {
12487
12780
  ok: true,
12488
12781
  path,
12489
12782
  title: title?.trim() || "Plan",
12490
- content,
12491
12783
  message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
12492
12784
  };
12493
12785
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
@@ -12517,9 +12809,6 @@ async function startMcpServer() {
12517
12809
  datasource: args.datasource ?? (args.resource ? "inline" : "brightsy"),
12518
12810
  resource_id: args.resource_id,
12519
12811
  record_id: args.record_id,
12520
- resource: args.resource,
12521
- record: args.record,
12522
- records: args.records,
12523
12812
  message: "Schema pane accepted. Sideboard desktop opens the CMS column beside chat."
12524
12813
  };
12525
12814
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
@@ -12547,739 +12836,741 @@ async function startMcpServer() {
12547
12836
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
12548
12837
  }
12549
12838
  );
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;
12839
+ if (sideboardMcpProfile() !== "worktree") {
12840
+ const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
12841
+ const accountDefaults = resolveThreadDefaults2();
12842
+ const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
12843
+ server.tool(
12844
+ "create_thread",
12845
+ `Create a new worktree thread (chat) from branch, pr, or ticket. Pass repoPath from list_workspaces. From an orchestration chat, omit parentThreadId (Sideboard binds the child to this chat) or pass the exact id from the turn reminder \u2014 never invent a uuid. Prefer omitting agent/model so Sideboard applies ${accountDefaultsHint}. Then use send_to_thread to chat.`,
12846
+ {
12847
+ sourceType: import_zod.z.enum(["branch", "pr", "ticket"]),
12848
+ sourceRef: import_zod.z.string(),
12849
+ agent: import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
12850
+ model: import_zod.z.string().nullable().optional().describe(
12851
+ `Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
12852
+ ),
12853
+ repoPath: import_zod.z.string(),
12854
+ title: import_zod.z.string().optional(),
12855
+ parentThreadId: import_zod.z.string().optional().describe(
12856
+ "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
12857
+ )
12858
+ },
12859
+ async (args) => {
12860
+ const envParentId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || "";
12861
+ let parentId = args.parentThreadId?.trim() || "";
12862
+ let parent = parentId ? orch.getThread(parentId) : null;
12863
+ let parentCorrectedFrom;
12864
+ if (envParentId) {
12865
+ const envParent = orch.getThread(envParentId);
12866
+ if (envParent) {
12867
+ if (parentId && parentId !== envParentId) parentCorrectedFrom = parentId;
12868
+ else if (!parentId) parentCorrectedFrom = void 0;
12869
+ parentId = envParentId;
12870
+ parent = envParent;
12871
+ }
12581
12872
  }
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) {
12873
+ if (parentId && !parent) {
12874
+ parentCorrectedFrom = parentId;
12875
+ parentId = "";
12876
+ parent = null;
12877
+ }
12878
+ if (parentId) {
12879
+ const children = orch.getThreads(false).filter((t) => t.parentThreadId === parentId);
12880
+ if (children.length >= MAX_ORCH_THREADS) {
12881
+ return {
12882
+ content: [
12883
+ {
12884
+ type: "text",
12885
+ text: `Thread-creation cap (${MAX_ORCH_THREADS}) reached for this orchestration session`
12886
+ }
12887
+ ],
12888
+ isError: true
12889
+ };
12890
+ }
12891
+ }
12892
+ let agentArg = args.agent;
12893
+ let agentCoercedFrom;
12894
+ const resolvedProbe = resolveNewThreadOptions2({ agent: agentArg }).agent;
12895
+ if (resolvedProbe === "codex") {
12896
+ agentCoercedFrom = agentArg ?? "codex";
12897
+ const accountAgent = resolveNewThreadOptions2({}).agent;
12898
+ agentArg = accountAgent !== "codex" ? accountAgent : "cursor";
12899
+ }
12900
+ const opts = resolveNewThreadOptions2({
12901
+ agent: agentArg,
12902
+ model: args.model
12903
+ });
12904
+ try {
12905
+ const thread = await withTimeout(
12906
+ orch.createThread({
12907
+ sourceType: args.sourceType,
12908
+ sourceRef: args.sourceRef,
12909
+ agent: opts.agent,
12910
+ model: opts.model,
12911
+ effort: opts.effort,
12912
+ fast: opts.fast,
12913
+ repoPath: args.repoPath,
12914
+ title: args.title,
12915
+ parentThreadId: parentId || null
12916
+ }),
12917
+ CREATE_THREAD_TIMEOUT_MS,
12918
+ "create_thread"
12919
+ );
12920
+ const parentNote = parentCorrectedFrom ? parentId ? `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; nested under ${parentId}` : `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; created without parent` : void 0;
12921
+ return {
12922
+ content: [
12923
+ {
12924
+ type: "text",
12925
+ text: JSON.stringify({
12926
+ id: thread.id,
12927
+ title: thread.title,
12928
+ branchName: thread.branchName,
12929
+ worktreePath: thread.worktreePath,
12930
+ agent: thread.agent,
12931
+ model: thread.model,
12932
+ status: thread.status,
12933
+ link: `sideboard://thread/${thread.id}`,
12934
+ parentThreadId: thread.parentThreadId,
12935
+ ...agentCoercedFrom ? {
12936
+ agentCoercedFrom,
12937
+ note: `Avoid nested Codex under a Codex orchestrator \u2014 used Account default agent=${thread.agent}`
12938
+ } : {},
12939
+ ...parentCorrectedFrom ? { parentCorrectedFrom, parentNote } : {}
12940
+ })
12941
+ }
12942
+ ]
12943
+ };
12944
+ } catch (err) {
12945
+ const message = err instanceof Error ? err.message : String(err);
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: `create_thread failed: ${message}`
12596
12951
  }
12597
12952
  ],
12598
12953
  isError: true
12599
12954
  };
12600
12955
  }
12601
12956
  }
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;
12957
+ );
12958
+ server.tool(
12959
+ "send_to_thread",
12960
+ "Queue a prompt on a worktree thread chat (runs under concurrency cap). Use after create_thread to start or continue a conversation. Set force_stop=true to interrupt an in-flight/queued turn (kill + clear queue) before queueing this prompt \u2014 use when the thread is mid-turn or has stale queued prompts you need to replace.",
12961
+ {
12962
+ ref: import_zod.z.string(),
12963
+ prompt: import_zod.z.string(),
12964
+ force_stop: import_zod.z.boolean().optional()
12965
+ },
12966
+ async ({ ref, prompt, force_stop }) => {
12967
+ if (force_stop) {
12968
+ const existing = orch.getThread(ref);
12969
+ if (existing) {
12970
+ orch.stop(ref, { clearQueue: true });
12971
+ }
12972
+ }
12973
+ const thread = await orch.send(ref, prompt);
12631
12974
  return {
12632
12975
  content: [
12633
12976
  {
12634
12977
  type: "text",
12635
12978
  text: JSON.stringify({
12636
12979
  id: thread.id,
12637
- title: thread.title,
12638
- branchName: thread.branchName,
12639
- worktreePath: thread.worktreePath,
12640
- agent: thread.agent,
12641
- model: thread.model,
12642
12980
  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 } : {}
12981
+ queueLength: thread.queue.length,
12982
+ forceStopped: Boolean(force_stop)
12650
12983
  })
12651
12984
  }
12652
12985
  ]
12653
12986
  };
12654
- } catch (err) {
12655
- const message = err instanceof Error ? err.message : String(err);
12987
+ }
12988
+ );
12989
+ server.tool(
12990
+ "wait_for_turn",
12991
+ "Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread to read the agent reply.",
12992
+ {
12993
+ ref: import_zod.z.string(),
12994
+ timeoutMs: import_zod.z.number().optional()
12995
+ },
12996
+ async ({ ref, timeoutMs }) => {
12997
+ const thread = await orch.waitForTurn(ref, timeoutMs ?? 6e5);
12998
+ const result = orch.getTurnResult(thread.id);
12656
12999
  return {
12657
13000
  content: [
12658
13001
  {
12659
13002
  type: "text",
12660
- text: `create_thread failed: ${message}`
13003
+ text: JSON.stringify({
13004
+ id: thread.id,
13005
+ status: result.status,
13006
+ text: result.text
13007
+ })
12661
13008
  }
12662
- ],
12663
- isError: true
13009
+ ]
12664
13010
  };
12665
13011
  }
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
- }
13012
+ );
13013
+ server.tool(
13014
+ "get_turn_result",
13015
+ "Final assistant message only (not full transcript)",
13016
+ { ref: import_zod.z.string() },
13017
+ async ({ ref }) => {
13018
+ const result = orch.getTurnResult(ref);
13019
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
12682
13020
  }
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) {
13021
+ );
13022
+ server.tool(
13023
+ "stop_thread",
13024
+ "Force-stop a thread: kill any in-flight agent turn AND clear queued prompts so drainQueue cannot continue. Does not archive the worktree. Optional force defaults to true.",
13025
+ {
13026
+ ref: import_zod.z.string(),
13027
+ force: import_zod.z.boolean().optional()
13028
+ },
13029
+ async ({ ref, force }) => {
13030
+ const t = orch.getThread(ref);
13031
+ if (!t) {
13032
+ return {
13033
+ content: [{ type: "text", text: `Thread not found: ${ref}` }],
13034
+ isError: true
13035
+ };
13036
+ }
13037
+ const clearQueue = force !== false;
13038
+ const hadQueued = t.queue.length > 0;
13039
+ const stopped = orch.stop(ref, { clearQueue });
12742
13040
  return {
12743
- content: [{ type: "text", text: `Thread not found: ${ref}` }],
12744
- isError: true
13041
+ content: [
13042
+ {
13043
+ type: "text",
13044
+ text: JSON.stringify({
13045
+ id: stopped.id,
13046
+ status: stopped.status,
13047
+ clearedQueue: clearQueue && hadQueued
13048
+ })
13049
+ }
13050
+ ]
12745
13051
  };
12746
13052
  }
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) {
13053
+ );
13054
+ server.tool(
13055
+ "archive_thread",
13056
+ "Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Cannot archive the cloud coordinator \u2014 use the Sideboard UI for that.",
13057
+ { ref: import_zod.z.string() },
13058
+ async ({ ref }) => {
13059
+ const t = orch.getThread(ref);
13060
+ if (!t) {
13061
+ return {
13062
+ content: [{ type: "text", text: `Thread not found: ${ref}` }],
13063
+ isError: true
13064
+ };
13065
+ }
13066
+ const blocked = mcpArchiveBlockedReason(t);
13067
+ if (blocked) {
13068
+ return {
13069
+ content: [{ type: "text", text: blocked }],
13070
+ isError: true
13071
+ };
13072
+ }
13073
+ const archived = await orch.archive(ref);
12771
13074
  return {
12772
- content: [{ type: "text", text: `Thread not found: ${ref}` }],
12773
- isError: true
13075
+ content: [
13076
+ {
13077
+ type: "text",
13078
+ text: JSON.stringify({
13079
+ id: archived.id,
13080
+ status: archived.status
13081
+ })
13082
+ }
13083
+ ]
12774
13084
  };
12775
13085
  }
12776
- const blocked = mcpArchiveBlockedReason(t);
12777
- if (blocked) {
12778
- return {
12779
- content: [{ type: "text", text: blocked }],
12780
- isError: true
12781
- };
13086
+ );
13087
+ server.tool(
13088
+ "restore_thread",
13089
+ "Restore an archived thread (recreates worktree from branch when needed)",
13090
+ { ref: import_zod.z.string() },
13091
+ async ({ ref }) => {
13092
+ try {
13093
+ const restored = await orch.restore(ref);
13094
+ return {
13095
+ content: [
13096
+ {
13097
+ type: "text",
13098
+ text: JSON.stringify({
13099
+ id: restored.id,
13100
+ status: restored.status,
13101
+ worktreePath: restored.worktreePath
13102
+ })
13103
+ }
13104
+ ]
13105
+ };
13106
+ } catch (err) {
13107
+ const message = err instanceof Error ? err.message : String(err);
13108
+ return { content: [{ type: "text", text: message }], isError: true };
13109
+ }
12782
13110
  }
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);
13111
+ );
13112
+ server.tool(
13113
+ "get_diff",
13114
+ "Compact diff summary (capped hunks, paginated)",
13115
+ {
13116
+ ref: import_zod.z.string(),
13117
+ maxFiles: import_zod.z.number().optional()
13118
+ },
13119
+ async ({ ref, maxFiles }) => {
13120
+ const summary = await orch.diffSummary(ref);
12804
13121
  return {
12805
13122
  content: [
12806
13123
  {
12807
13124
  type: "text",
12808
13125
  text: JSON.stringify({
12809
- id: restored.id,
12810
- status: restored.status,
12811
- worktreePath: restored.worktreePath
13126
+ ...summary,
13127
+ files: summary.files.slice(0, maxFiles ?? 10)
12812
13128
  })
12813
13129
  }
12814
13130
  ]
12815
13131
  };
12816
- } catch (err) {
12817
- const message = err instanceof Error ? err.message : String(err);
12818
- return { content: [{ type: "text", text: message }], isError: true };
12819
13132
  }
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
- })
13133
+ );
13134
+ server.tool(
13135
+ "request_review",
13136
+ 'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .sideboard/review.md when present (else local Review request.md / stock template), and sends "Review changes in this workspace." Expect Approve / Approve with nits / Request changes / Needs more information. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn / get_turn_result on the returned review tab id.',
13137
+ { ref: import_zod.z.string().describe("Worktree thread id/ref to review") },
13138
+ async ({ ref }) => {
13139
+ try {
13140
+ const tab = await orch.requestReview(ref);
13141
+ const from = orch.getThread(ref);
13142
+ return {
13143
+ content: [
13144
+ {
13145
+ type: "text",
13146
+ text: JSON.stringify({
13147
+ id: tab.id,
13148
+ title: tab.title,
13149
+ status: tab.status,
13150
+ fromThreadId: from?.id ?? ref,
13151
+ link: `sideboard://thread/${tab.id}`
13152
+ })
13153
+ }
13154
+ ]
13155
+ };
13156
+ } catch (err) {
13157
+ const message = err instanceof Error ? err.message : String(err);
13158
+ return { content: [{ type: "text", text: message }], isError: true };
13159
+ }
13160
+ }
13161
+ );
13162
+ const agentEnum = import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
13163
+ server.tool(
13164
+ "list_models",
13165
+ "List models for an agent. Prefer Auto: do not call this unless you have a reason to pin a specific model (user request, cost/latency, capability). Omit agent to list all.",
13166
+ {
13167
+ agent: agentEnum.optional().describe("Limit to one agent; omit for all")
13168
+ },
13169
+ async ({ agent }) => {
13170
+ try {
13171
+ const catalogs = await listModelsForAgent(agent);
13172
+ return {
13173
+ content: [{ type: "text", text: JSON.stringify(catalogs, null, 2) }]
13174
+ };
13175
+ } catch (err) {
13176
+ const message = err instanceof Error ? err.message : String(err);
13177
+ return { content: [{ type: "text", text: message }], isError: true };
13178
+ }
13179
+ }
13180
+ );
13181
+ server.tool(
13182
+ "fork_worktree",
13183
+ "Fork a worktree agent chat into a NEW git worktree + chat (desktop \u201CFork to new workspace\u201D). Seeds a transcript (through through_index, default all). Optional agent override. Leave model unset for Auto (default) \u2014 only pass model when you have a reason. Not for the orchestrator. Then send_to_thread / wait_for_turn on the returned id.",
13184
+ {
13185
+ ref: import_zod.z.string().describe("Worktree thread id/ref to fork"),
13186
+ through_index: import_zod.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
13187
+ agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
13188
+ model: import_zod.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
13189
+ title: import_zod.z.string().optional()
13190
+ },
13191
+ async ({ ref, through_index, agent, model, title }) => {
13192
+ try {
13193
+ const source = orch.getThread(ref);
13194
+ if (source) await orch.reconcile(source.repoPath, { drainQueues: false });
13195
+ const thread = await orch.forkThreadWorktree({
13196
+ threadId: ref,
13197
+ throughIndex: through_index,
13198
+ agent,
13199
+ model,
13200
+ title
13201
+ });
13202
+ return {
13203
+ content: [
13204
+ {
13205
+ type: "text",
13206
+ text: JSON.stringify({
13207
+ id: thread.id,
13208
+ title: thread.title,
13209
+ status: thread.status,
13210
+ agent: thread.agent,
13211
+ model: thread.model,
13212
+ branchName: thread.branchName,
13213
+ worktreePath: thread.worktreePath,
13214
+ fromThreadId: source?.id ?? ref,
13215
+ link: `sideboard://thread/${thread.id}`
13216
+ })
13217
+ }
13218
+ ]
13219
+ };
13220
+ } catch (err) {
13221
+ const message = err instanceof Error ? err.message : String(err);
13222
+ return { content: [{ type: "text", text: message }], isError: true };
13223
+ }
13224
+ }
13225
+ );
13226
+ server.tool(
13227
+ "fork_chat",
13228
+ "Fork a chat into a NEW tab on the SAME workspace: worktree agent \u2192 same worktree tab; Global orchestration chat \u2192 new orchestration chat (same synthetic home). Seeds a transcript; optional agent override. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Remote coordinators use this to continue an orchestration chat on another agent after session limits. Then send_to_thread / wait_for_turn on the returned id. Use fork_worktree only for worktree agents that need a new git worktree.",
13229
+ {
13230
+ ref: import_zod.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
13231
+ through_index: import_zod.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
13232
+ agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
13233
+ model: import_zod.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
13234
+ title: import_zod.z.string().optional()
13235
+ },
13236
+ async ({ ref, through_index, agent, model, title }) => {
13237
+ try {
13238
+ const source = orch.getThread(ref);
13239
+ if (!source) {
13240
+ return {
13241
+ content: [{ type: "text", text: `Thread not found: ${ref}` }],
13242
+ isError: true
13243
+ };
12839
13244
  }
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);
13245
+ const tab = orch.forkChatTab({
13246
+ threadId: source.id,
13247
+ throughIndex: through_index,
13248
+ agent,
13249
+ model,
13250
+ title
13251
+ });
13252
+ return {
13253
+ content: [
13254
+ {
13255
+ type: "text",
13256
+ text: JSON.stringify({
13257
+ id: tab.id,
13258
+ title: tab.title,
13259
+ status: tab.status,
13260
+ agent: tab.agent,
13261
+ model: tab.model,
13262
+ sourceType: tab.sourceType,
13263
+ worktreePath: tab.worktreePath,
13264
+ fromThreadId: source.id,
13265
+ link: `sideboard://thread/${tab.id}`
13266
+ })
13267
+ }
13268
+ ]
13269
+ };
13270
+ } catch (err) {
13271
+ const message = err instanceof Error ? err.message : String(err);
13272
+ return { content: [{ type: "text", text: message }], isError: true };
13273
+ }
13274
+ }
13275
+ );
13276
+ server.tool(
13277
+ "run_dev_script",
13278
+ "Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
13279
+ {
13280
+ ref: import_zod.z.string(),
13281
+ name: import_zod.z.string().optional()
13282
+ },
13283
+ async ({ ref, name }) => {
13284
+ const result = await orch.startDev(ref, name);
12852
13285
  return {
12853
13286
  content: [
12854
13287
  {
12855
13288
  type: "text",
12856
13289
  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}`
13290
+ port: result.port,
13291
+ scriptName: result.scriptName,
13292
+ ports: result.ports,
13293
+ url: `http://localhost:${result.port}`
12862
13294
  })
12863
13295
  }
12864
13296
  ]
12865
13297
  };
12866
- } catch (err) {
12867
- const message = err instanceof Error ? err.message : String(err);
12868
- return { content: [{ type: "text", text: message }], isError: true };
12869
13298
  }
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);
13299
+ );
13300
+ server.tool(
13301
+ "list_run_scripts",
13302
+ "List named run scripts available for a thread",
13303
+ { ref: import_zod.z.string() },
13304
+ async ({ ref }) => {
13305
+ const scripts = orch.listThreadRunScripts(ref);
13306
+ const active = orch.getActiveRuns(ref);
12882
13307
  return {
12883
- content: [{ type: "text", text: JSON.stringify(catalogs, null, 2) }]
13308
+ content: [
13309
+ {
13310
+ type: "text",
13311
+ text: JSON.stringify({ scripts, active }, null, 2)
13312
+ }
13313
+ ]
12884
13314
  };
12885
- } catch (err) {
12886
- const message = err instanceof Error ? err.message : String(err);
12887
- return { content: [{ type: "text", text: message }], isError: true };
12888
13315
  }
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
13316
+ );
13317
+ server.tool(
13318
+ "stop_dev_script",
13319
+ "Stop a running script for a thread (all scripts if name omitted)",
13320
+ {
13321
+ ref: import_zod.z.string(),
13322
+ name: import_zod.z.string().optional()
13323
+ },
13324
+ async ({ ref, name }) => {
13325
+ orch.stopDev(ref, name);
13326
+ return { content: [{ type: "text", text: "ok" }] };
13327
+ }
13328
+ );
13329
+ server.tool(
13330
+ "run_setup",
13331
+ "Re-run workspace setup (Sideboard/Conductor settings or .cursor/worktrees.json)",
13332
+ { ref: import_zod.z.string() },
13333
+ async ({ ref }) => {
13334
+ const result = await orch.runSetup(ref);
13335
+ return {
13336
+ content: [{ type: "text", text: JSON.stringify(result) }]
13337
+ };
13338
+ }
13339
+ );
13340
+ server.tool(
13341
+ "add_workspace",
13342
+ "Register a git repo as a Sideboard workspace",
13343
+ { repoPath: import_zod.z.string() },
13344
+ async ({ repoPath }) => {
13345
+ const ws = await orch.addWorkspace(repoPath);
13346
+ return { content: [{ type: "text", text: JSON.stringify(ws) }] };
13347
+ }
13348
+ );
13349
+ server.tool(
13350
+ "remove_workspace",
13351
+ "Unregister a Sideboard workspace (does not archive threads)",
13352
+ { repoPath: import_zod.z.string() },
13353
+ async ({ repoPath }) => {
13354
+ orch.removeWorkspace(repoPath);
13355
+ return { content: [{ type: "text", text: "ok" }] };
13356
+ }
13357
+ );
13358
+ server.tool(
13359
+ "fanout",
13360
+ "Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
13361
+ {
13362
+ prompt: import_zod.z.string(),
13363
+ agents: import_zod.z.array(import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
13364
+ repoPath: import_zod.z.string(),
13365
+ sourceType: import_zod.z.enum(["branch", "pr", "ticket"]).optional(),
13366
+ sourceRef: import_zod.z.string().optional(),
13367
+ title: import_zod.z.string().optional()
13368
+ },
13369
+ async (args) => {
13370
+ const threads = await orch.bestOfN(args);
13371
+ return {
13372
+ content: [
13373
+ {
13374
+ type: "text",
13375
+ text: JSON.stringify(
13376
+ threads.map((t) => ({
13377
+ id: t.id,
13378
+ agent: t.agent,
13379
+ branchName: t.branchName,
13380
+ worktreePath: t.worktreePath
13381
+ })),
13382
+ null,
13383
+ 2
13384
+ )
13385
+ }
13386
+ ]
13387
+ };
13388
+ }
13389
+ );
13390
+ server.tool(
13391
+ "list_branches",
13392
+ "List git branches in a registered workspace. Pass repoPath from list_workspaces (unmerged into the default branch by default \u2014 for create_thread sourceType=branch).",
13393
+ {
13394
+ repoPath: import_zod.z.string(),
13395
+ unmergedOnly: import_zod.z.boolean().optional()
13396
+ },
13397
+ async ({ repoPath, unmergedOnly }) => {
13398
+ const root = await resolveRepoRoot(repoPath);
13399
+ const branches = await listBranches(root, {
13400
+ unmergedOnly: unmergedOnly !== false
12911
13401
  });
12912
13402
  return {
12913
13403
  content: [
12914
13404
  {
12915
13405
  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
- })
13406
+ text: branches.map((b) => `${b.current ? "*" : " "} ${b.name}`).join("\n")
12927
13407
  }
12928
13408
  ]
12929
13409
  };
12930
- } catch (err) {
12931
- const message = err instanceof Error ? err.message : String(err);
12932
- return { content: [{ type: "text", text: message }], isError: true };
12933
13410
  }
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
13411
+ );
13412
+ server.tool(
13413
+ "list_prs",
13414
+ "List open GitHub PRs for a registered workspace. Pass repoPath from list_workspaces (uses gh against that repo remote). Then create_thread with sourceType=pr.",
13415
+ { repoPath: import_zod.z.string() },
13416
+ async ({ repoPath }) => {
13417
+ const root = await resolveRepoRoot(repoPath);
13418
+ const prs = await listPrs(root);
13419
+ return {
13420
+ content: [
13421
+ {
13422
+ type: "text",
13423
+ text: prs.map(
13424
+ (p) => `#${p.number} ${p.title} (${p.headRefName})${p.isCrossRepository ? " [fork]" : ""}`
13425
+ ).join("\n")
13426
+ }
13427
+ ]
13428
+ };
13429
+ }
13430
+ );
13431
+ server.tool(
13432
+ "get_pr_stack",
13433
+ "Load the GitHub PR stack for a thread worktree (`gh stack view --json`). Returns null JSON when the branch is not stacked. Prefer this before mergePr on stacked PRs.",
13434
+ { ref: import_zod.z.string() },
13435
+ async ({ ref }) => {
13436
+ const stack = await orch.getPrStack(ref);
13437
+ return {
13438
+ content: [{ type: "text", text: JSON.stringify(stack, null, 2) }]
13439
+ };
13440
+ }
13441
+ );
13442
+ server.tool(
13443
+ "open_pr_stack_layers",
13444
+ "Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
13445
+ {
13446
+ ref: import_zod.z.string(),
13447
+ layer: import_zod.z.number().int().positive().optional()
13448
+ },
13449
+ async ({ ref, layer }) => {
13450
+ const result = await orch.openPrStackLayers(ref, { layer });
13451
+ return {
13452
+ content: [
13453
+ {
13454
+ type: "text",
13455
+ text: JSON.stringify(
13456
+ {
13457
+ stackNumber: result.stack.stackNumber,
13458
+ trunk: result.stack.trunk,
13459
+ threads: result.threads.map((t) => ({
13460
+ id: t.id,
13461
+ title: t.title,
13462
+ branchName: t.branchName,
13463
+ stackLayer: t.stackLayer,
13464
+ worktreePath: t.worktreePath,
13465
+ prUrl: t.prUrl,
13466
+ link: `sideboard://thread/${t.id}`
13467
+ }))
13468
+ },
13469
+ null,
13470
+ 2
13471
+ )
13472
+ }
13473
+ ]
13474
+ };
13475
+ }
13476
+ );
13477
+ server.tool(
13478
+ "add_stack_layer",
13479
+ "Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
13480
+ {
13481
+ ref: import_zod.z.string(),
13482
+ branchName: import_zod.z.string(),
13483
+ title: import_zod.z.string().optional()
13484
+ },
13485
+ async ({ ref, branchName, title }) => {
13486
+ const result = await orch.addStackLayer(ref, branchName, { title });
13487
+ return {
13488
+ content: [
13489
+ {
13490
+ type: "text",
13491
+ text: JSON.stringify(
13492
+ {
13493
+ id: result.thread.id,
13494
+ title: result.thread.title,
13495
+ branchName: result.thread.branchName,
13496
+ stackLayer: result.thread.stackLayer,
13497
+ worktreePath: result.thread.worktreePath,
13498
+ link: `sideboard://thread/${result.thread.id}`
13499
+ },
13500
+ null,
13501
+ 2
13502
+ )
13503
+ }
13504
+ ]
13505
+ };
13506
+ }
13507
+ );
13508
+ server.tool(
13509
+ "create_pr_stack",
13510
+ "Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
13511
+ {
13512
+ repoPath: import_zod.z.string(),
13513
+ branches: import_zod.z.array(import_zod.z.string()).min(1),
13514
+ agent: import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
13515
+ base: import_zod.z.string().optional(),
13516
+ title: import_zod.z.string().optional()
13517
+ },
13518
+ async (args) => {
13519
+ const result = await orch.createPrStack({
13520
+ repoPath: args.repoPath,
13521
+ branches: args.branches,
13522
+ agent: args.agent,
13523
+ base: args.base,
13524
+ title: args.title
12961
13525
  });
12962
13526
  return {
12963
13527
  content: [
12964
13528
  {
12965
13529
  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
- })
13530
+ text: JSON.stringify(
13531
+ {
13532
+ stackNumber: result.stack.stackNumber,
13533
+ trunk: result.stack.trunk,
13534
+ threads: result.threads.map((t) => ({
13535
+ id: t.id,
13536
+ title: t.title,
13537
+ branchName: t.branchName,
13538
+ stackLayer: t.stackLayer,
13539
+ worktreePath: t.worktreePath,
13540
+ link: `sideboard://thread/${t.id}`
13541
+ }))
13542
+ },
13543
+ null,
13544
+ 2
13545
+ )
12977
13546
  }
12978
13547
  ]
12979
13548
  };
12980
- } catch (err) {
12981
- const message = err instanceof Error ? err.message : String(err);
12982
- return { content: [{ type: "text", text: message }], isError: true };
12983
13549
  }
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
- );
13550
+ );
13551
+ server.tool(
13552
+ "list_issues",
13553
+ "List issues from Sideboard Account connections (Linear API or GitHub Issues; Linear\u2192GitHub fallback when Linear is not connected). Pass repoPath from list_workspaces \u2014 GitHub Issues are scoped to that repo. Then create_thread with sourceType=ticket.",
13554
+ { repoPath: import_zod.z.string() },
13555
+ async ({ repoPath }) => {
13556
+ const root = await resolveRepoRoot(repoPath);
13557
+ const result = await listIssues(root);
13558
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
13559
+ }
13560
+ );
13561
+ server.tool(
13562
+ "list_linear_issues",
13563
+ "Deprecated: prefer list_issues. Lists assigned Linear issues via the chosen agent MCP connector",
13564
+ {
13565
+ agent: import_zod.z.enum(["claude", "codex", "opencode"]),
13566
+ repoPath: import_zod.z.string()
13567
+ },
13568
+ async ({ agent, repoPath }) => {
13569
+ const issues = await listLinearIssues(agent, repoPath);
13570
+ return { content: [{ type: "text", text: JSON.stringify(issues, null, 2) }] };
13571
+ }
13572
+ );
13573
+ }
13283
13574
  const transport = new import_stdio.StdioServerTransport();
13284
13575
  await server.connect(transport);
13285
13576
  }